Introduction
It often happens to me that after installing my project on Windows platform, sometimes some commands will not work in
the project.
This is because the corresponding environment variables are not properly updated. To fix the problem recently
I compared my system environment variables with my team member's system manually. It required a very large effort. Actually this effort is
a waste of time :-). Also I understood that many CodeProject members were also facing the same problem. Finally
I decided to develop a tool for comparing environment variables. Hope it is a great decision, :-)
What is an Environment Variable?
An environment variable is a dynamic "object" on a computer that stores a value, which in turn can be referenced by one or more software programs in
Windows. Environment variables help programs know what directory to install files in, where to store temporary files, where to find user profile settings, and many other things. It can be said that environment variables help to create and shape the environment of where a program runs.
Background
What is Environment Variable Compare?
Environment Variable Compare is a tool which used to compare environment variables of two systems in text format. Comparison results will be displayed in various color formats. Color setting will be done based on the following conditions.
- If two lines have different text, then the two lines will be displayed in red color.
- If left side file contains a line(s) which is not present in the right side file; then corresponding new line(s)
in the left side file will be displayed in blue color.
- If right side file contains a line(s) which is not present in the left side file; then
the corresponding new line(s) in the right side will displayed in blue color.
Note: To get environment variables as text files you can use
the set
keyword in the command prompt. [Example: Run this command on
the command prompt "set > C:\FirstDemoFile.txt.]
This environment variable compare tool introduces various miscellaneous features to
the user. I hope these features will make this a stunning tool. The purpose and implementation details of each and every technique used in this tool will be explained later.
Application Outlook
Mainly the application contains two list controls separated by a splitter. Actually this is not a splitter window application. Here a picture control acts like
a splitter. It is very simple to customize a picture control into a splitter. The customized class structure is mentioned in
the below lines of code:
class FlatSplitterWnd : public CSplitterWnd
{
DECLARE_DYNAMIC(FlatSplitterWnd)
public:
FlatSplitterWnd();
virtual ~FlatSplitterWnd();
virtual void OnDrawSplitter( CDC* pDc_i, ESplitType ntype, const CRect& cRect );
protected:
DECLARE_MESSAGE_MAP()
};
The logic of drawing the splitter is explained in the below lines of code.
void FlatSplitterWnd::OnDrawSplitter( CDC* pDc_i, ESplitType ntype, const CRect& cRect )
{
try
{
CRect BorderRect( cRect );
if( NULL == pDc_i )
{
return;
}
if(( splitBorder != ntype ))
{
CSplitterWnd::OnDrawSplitter(pDc_i, ntype, cRect);
}
else
{
pDc_i->Draw3dRect( BorderRect,
GetSysColor(COLOR_BTNSHADOW), GetSysColor(COLOR_BTNHIGHLIGHT));
BorderRect.DeflateRect( 1, 1, 1, 1 );
pDc_i->Draw3dRect( BorderRect,
GetSysColor(COLOR_BTNHIGHLIGHT), GetSysColor(COLOR_BTNSHADOW));
RecalcLayout();
}
}
catch( CMemoryException* pMemoryException )
{
ExceptionHandler::DumpMFCException( _T( "FlatSplitterWnd::OnDrawSplitter()" ),
pMemoryException, _T( __FILE__ ), __LINE__ );
}
catch( CException* pException )
{
ExceptionHandler::DumpMFCException( _T( "FlatSplitterWnd::OnDrawSplitter()" ),
pException, _T( __FILE__ ), __LINE__ );
}
catch( ... )
{
ExceptionHandler::DumpUnknownException( _T( "FlatSplitterWnd::OnDrawSplitter()" ),
_T( __FILE__ ), __LINE__ );
}
}
Comparison logic
Comparison of environment variables is performed on a line by line basis. After loading
the first environment text file it checks if both sides contain files. If both sides contain environment variable text files then it starts to compare. To start the comparison first it parses the files from both sides to identify the different environment variables and
the new environment variables in both sides. Based on the line status it makes a “Compare display” for
the user. To differentiate the files, internally it sets some status to the lines
on both sides. To store environment variable status on both sides it uses a structure called FILE_INFO
.
typedef struct FILE_INFO_t
{
CString csEnvironmentVariable; CString csVaribleValue; CString csFullVariable; ROW_TYPE_e eStoreType;
FILE_INFO_t();
~FILE_INFO_t();
FILE_INFO_t( const FILE_INFO_t& stFileInfo_i );
const FILE_INFO_t& operator=( FILE_INFO_t& stFileInfo_i );
void Clear();
}FILE_INFO_t;
After getting entire line details from both sides, then it displays lines on both sides based on this compared result. The below section of code shows the comparison:
bool CEnvironmentVarCompareView::CompareFilesBasedOnLeftAsSourse()
{
try
{
m_LeftListCtrl.DeleteAllItems();
m_RightListCtrl.DeleteAllItems();
int nLeftListSize = m_csLeftListCtrlList.GetSize();
int nRightListSize = m_csRightListCtrlList.GetSize();
if( nLeftListSize >= nRightListSize )
{
FILE_INFO_t stFileInfo;
FILE_INFO_t stLeftFileMapLookUpInfo;
FILE_INFO_t stRightFileMapLookUpInfo;
CStringArray csRightListCtrlItem;
CString csEnvVar;
int nItem = 0;
POSITION Pos = m_csLeftListCtrlList.GetHeadPosition();
while( NULL != Pos )
{
stFileInfo = m_csLeftListCtrlList.GetNext( Pos );
if( NULL != m_LeftListCtrl.m_FileInfoMap.Lookup( stFileInfo.csEnvironmentVariable,
stLeftFileMapLookUpInfo ))
{
if( NEW_ROW != stLeftFileMapLookUpInfo.eStoreType )
{
m_LeftListCtrl.InsertItem( nItem, stLeftFileMapLookUpInfo.csFullVariab;
if( NULL != m_RightListCtrl.m_FileInfoMap.Lookup(
stLeftFileMapLookUpIefo.csEnvironmentVariable, stRightFileMapLookUpInfo ))
{
m_RightListCtrl.InsertItem( nItem, stRightFileMapLookUpInfo.csFullVariable );
}
}
else
{
m_LeftListCtrl.InsertItem( nItem, stLeftFileMapLookUpInfo.csFullVariable );
m_RightListCtrl.InsertItem( nItem, SPACE_CHAR );
}
}
++nItem;
}
FILE_INFO_t stLookUpinfo;
FILE_INFO_t stMainLookUpInfo;
POSITION NewPos = m_csRightListCtrlList.GetHeadPosition();
int nItemPos = 0;
while( NULL != NewPos )
{
stMainLookUpInfo = m_csRightListCtrlList.GetNext( NewPos );
if( NULL != m_RightListCtrl.m_FileInfoMap.Lookup(
stMainLookUpInfo.csEnvironmentVariable, stLookUpinfo ))
{
if( NEW_ROW == stLookUpinfo.eStoreType )
{
m_RightListCtrl.InsertItem( nItemPos, stLookUpinfo.csFullVariable );
m_LeftListCtrl.InsertItem( nItemPos, SPACE_CHAR );
}
}
++nItemPos;
}
}
else
{
FILE_INFO_t stFileInfo;
FILE_INFO_t stLeftFileMapLookUpInfo;
FILE_INFO_t stRightFileMapLookUpInfo;
CStringArray csLeftListCtrlItemArray;
CString csEnvVar;
int nItem = 0;
POSITION Pos = m_csRightListCtrlList.GetHeadPosition();
while( NULL != Pos )
{
stFileInfo = m_csRightListCtrlList.GetNext( Pos );
if( NULL != m_RightListCtrl.m_FileInfoMap.Lookup(
stFileInfo.csEnvironmentVariable, stRightFileMapLookUpInfo ))
{
if( NEW_ROW != stRightFileMapLookUpInfo.eStoreType )
{
m_RightListCtrl.InsertItem( nItem, stRightFileMapLookUpInfo.csFullVariable );
if( NULL != m_LeftListCtrl.m_FileInfoMap.Lookup(
stFileInfo.csEnvironmentVariable, stLeftFileMapLookUpInfo ))
{
m_LeftListCtrl.InsertItem( nItem, stLeftFileMapLookUpInfo.csFullVariable );
csLeftListCtrlItemArray.Add( stLeftFileMapLookUpInfo.csEnvironmentVariable );
}
}
else
{
m_RightListCtrl.InsertItem( nItem, stRightFileMapLookUpInfo.csFullVariable );
m_LeftListCtrl.InsertItem( nItem, SPACE_CHAR );
csLeftListCtrlItemArray.Add( SPACE_CHAR );
}
}
++nItem;
}
FILE_INFO_t stLookUpinfo;
FILE_INFO_t stMainLookUpInfo;
POSITION NewPos = m_csLeftListCtrlList.GetHeadPosition();
int nItemPos = 0;
while( NULL != NewPos )
{
stMainLookUpInfo = m_csLeftListCtrlList.GetNext( NewPos );
if( NULL != m_LeftListCtrl.m_FileInfoMap.Lookup(
stMainLookUpInfo.csEnvironmentVariable, stLookUpinfo ))
{
if( NEW_ROW == stLookUpinfo.eStoreType )
{
m_LeftListCtrl.InsertItem( nItemPos, stLookUpinfo.csFullVariable );
m_RightListCtrl.InsertItem( nItemPos, SPACE_CHAR );
}
}
++nItemPos;
}
}
SettingItemData();
return true;
}
catch( CMemoryException* pMemoryException )
{
ExceptionHandler::DumpMFCException(
_T( "CEnvironmentVarCompareView::CompareFilesBasedOnLeftAsSourse()" ),
pMemoryException, _T( __FILE__ ), __LINE__ );
}
catch( CException* pException )
{
ExceptionHandler::DumpMFCException(
_T( "CEnvironmentVarCompareView::CompareFilesBasedOnLeftAsSourse()" ),
pException, _T( __FILE__ ), __LINE__ );
}
catch( ... )
{
ExceptionHandler::DumpUnknownException(
_T( "CEnvironmentVarCompareView::CompareFilesBasedOnLeftAsSourse()" ),
_T( __FILE__ ), __LINE__ );
}
return false;
}
Miscellaneous features
The environment variable compare tool provides lots of stunning features to
the user. They are:
1. Exception tracker
Environment variable compare uses a very powerful exception handling technique. It handles
the following types of exceptions. They are:
CResourceException
CMemoryException
CException
Application handles unknown exceptions also. If any exception occurs during program execution it will show
the corresponding exception details to the user and produce
an exception details text file in the current working directory. Refer to the figure below:
To identify the file, function, and line number it uses the following built-in macros. They are:
__FILE__
__LINE__
Each function should use this set exception handling code to get the exception details.
catch( CMemoryException* pMemoryException )
{
ExceptionHandler::DumpMFCException(
_T( "CEnvironmentVarCompareView::CompareFilesBasedOnLeftAsSourse()" ),
pMemoryException, _T( __FILE__ ), __LINE__ );
}
catch( CException* pException )
{
ExceptionHandler::DumpMFCException(
_T( "CEnvironmentVarCompareView::CompareFilesBasedOnLeftAsSourse()" ),
pException, _T( __FILE__ ), __LINE__ );
}
catch( ... )
{
ExceptionHandler::DumpUnknownException(
_T( "CEnvironmentVarCompareView::CompareFilesBasedOnLeftAsSourse()" ),
_T( __FILE__ ), __LINE__ );
}
To show a dialog and produce an exception report to the user it uses the following classes:
ExceptionHandler
ExceptionWatcher
Refer to the attached code to check how these classes are used. To use this type of exception handling in your code just add the above mentioned code in your application.
The Exception Handler class is used to get the exception details. After getting the exception details from
the appropriate handler, it passes this information to the ExceptionWatcher
class for displaying it in a dialog.
class ExceptionHandler
{
public:
ExceptionHandler();
~ExceptionHandler();
static void DumpMFCException( const CString& csFunctionName_i, CException* pException_i,
const CString& csFileName_i = _T( __FILE__ ),
int nLineNumber_i = __LINE__,
const CString& csMoreInfo_i = _T(" "));
static void DumpUnknownException( const CString& csFunctionName_i,
const CString& csFileName_i = _T( __FILE__ ),
int nLineNumber_i = __LINE__,
const CString& csMoreInfo_i = _T(" "));
static void HandleUnknownException( const CString& csFunctionName_i,
const CString& csFileName_i = _T( __FILE__ ),
int nLineNumber_i = __LINE__,
const CString& csMoreInfo_i = _T(" "));
static void DumpCOMException( const CString& csFunctionName_i, _com_error& ExceptionObject_i,
const CString& csFileName_i = _T( __FILE__ ),
int nLineNumber_i = __LINE__,
const CString& csMoreInfo_i = _T(" "));
static void DumpToFile( CString& csDumpDetails_i, const CString& csFileName_i, int& nlineNum_i,
const CString& csFunctionName_i );
static bool CreateAndDumpInfo( CString& csDumpInfo_i );
}
2. Error Trace
If an error happens during program execution, that will be logged into an application error trace. To log
an error into Error Trace, Environment Variable Compare uses a DLL called EnvLogInfo.dll. The
DLL contains an export function called WriteAppLog
.
extern "C" __declspec( dllexport ) void WriteAppLog( const CString csMsg_i )
{
try
{
TCHAR szDirectorPath[MAX_PATH];
GetModuleFileName( NULL, szDirectorPath, MAX_PATH );
CString csDirectoryPath = szDirectorPath;
int nPos = csDirectoryPath.ReverseFind( _T( '\\' ));
if( -1 != nPos )
{
CString csDir = csDirectoryPath.Left( nPos );
csDir += g_lpctszLogFile;
SYSTEMTIME stTime;
CString csDate;
CString csTime;
CString csDateTimeBind;
CStdioFile cFileOperator;
GetLocalTime( &stTime );
if( PathFileExists( csDir ))
{
if( !cFileOperator.Open( csDir,
CFile::modeWrite | CFile::shareDenyWrite ))
{
return;
}
cFileOperator.SeekToEnd();
csDate.Format( _T( "%d/%d/%d " ), stTime.wYear, stTime.wMonth, stTime.wDay );
csTime.Format( _T( "%d:%d:%d " ), stTime.wHour, stTime.wMinute, stTime.wSecond );
csDateTimeBind = csDate + _T( " " ) + csTime + _T( " " );
cFileOperator.WriteString( csDateTimeBind );
cFileOperator.WriteString( csMsg_i );
cFileOperator.WriteString( _T( "\n" ));
cFileOperator.Close();
}
else
{
if( !cFileOperator.Open( csDir, CFile::modeCreate |
CFile::modeWrite | CFile::shareDenyWrite ))
{
return;
}
csDate.Format( _T( "%d/%d/%d " ), stTime.wYear, stTime.wMonth, stTime.wDay );
csTime.Format( _T( "%d:%d:%d " ), stTime.wHour, stTime.wMinute, stTime.wSecond );
csDateTimeBind = csDate + _T( " " ) + csTime + _T( " " );
cFileOperator.WriteString( csDateTimeBind );
cFileOperator.WriteString( csMsg_i );
cFileOperator.WriteString( _T( "\n" ));
cFileOperator.Close();
}
}
}
catch( ... )
{
}
}
The figure below shows the error log.
3. Safety Mode
Don’t – Attackers can often control the value of important environment variables. You need to make sure that an attacker does not set environment variables to malicious values. In most cases, the information contained in the environment variables set by the shell can be determined by much more reliable means. Safety mode settings in
Environment Variable Compare provides three options to the user. They are:
a. Show warning before updating system Registry
This option is enabled when
the user updates the system Registry. Please refer to the figure below:
b. Create an automatic system environment variable back up
To improve environment variable security, each time an application is run it automatically takes a system environment variable back
up and saves it in the current execution directory. For this, Environment Variable
Compare uses a thread called RegistryBackupThreadProc()
.
c. Perform a system restart after updating system Registry
This is for ensuring proper update of environment variables in the system and all the processes running in that system.
bool SaftyMode::RestartSystem()
{
try
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
return( FALSE );
}
LookupPrivilegeValue( NULL, SE_SHUTDOWN_NAME,
&tkp.Privileges[0].Luid );
tkp.PrivilegeCount = 1; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges( hToken, FALSE, &tkp, 0,
(PTOKEN_PRIVILEGES)NULL, 0);
if ( GetLastError() != ERROR_SUCCESS )
{
return FALSE;
}
if( !ExitWindowsEx( EWX_REBOOT, SHTDN_REASON_MINOR_ENVIRONMENT ))
{
AfxMessageBox( _T( "Failed to restart windows" ));
CString csAppMsg;
csAppMsg.Format(
_T( "Failed to restart windows %s, Line num - %d" ),
_T( __FILE__ ), __LINE__);
WriteAppLog( csAppMsg );
}
return true;
}
catch( CMemoryException* pMemoryException )
{
ExceptionHandler::DumpMFCException( _T( "SaftyMode::RestartSystem()" ),
pMemoryException, _T( __FILE__ ), __LINE__ );
}
catch( CException* pException )
{
ExceptionHandler::DumpMFCException( _T( "SaftyMode::RestartSystem()" ),
pException, _T( __FILE__ ), __LINE__ );
}
catch( ... )
{
ExceptionHandler::DumpUnknownException( _T( "SaftyMode::RestartSystem()" ),
_T( __FILE__ ), __LINE__ );
}
return false;
}
4. Save Operation
Save option is one of the important features of the Environment Variable Compare tool. It permits the user
to do two types of savings. They are:
- File Saving
- Registry Saving
The figure below shows the user interface for the save operation.
File Saving
I already mentioned that
Environment Variable Compare uses two text files for comparing environment variables.
After the user makes changes in the comparison GUI, the user can update these changes directly in to files. To update
a file Environment Variable Compare uses a thread called FileWriteThreadProc.
Please find the below code for file update thread.
UINT _cdecl SaveVariable::FileWriteThreadProc( LPVOID pParam_i )
{
FILE_THREAD_FILE_INFO_t* pStFileThreadInfo =
static_cast<FILE_THREAD_FILE_INFO_t*>( pParam_i );
try
{
if( NULL == pStFileThreadInfo )
{
REPORT_OPERATION_INFO(
_T( "Unexpected error occured. File updation failed" ));
CString csAppMsg;
csAppMsg.Format(
_T( "Unexpected error occured. File updation "
"failed %s, Line num - %d" ), _T( __FILE__ ), __LINE__);
WriteAppLog( csAppMsg );
return 0;
}
CStdioFile csReadFile;
CString csWriteLine;
FILE_INFO_t stFileInfo;
csReadFile.Remove( pStFileThreadInfo->csPath );
HANDLE hHandle = CreateFile( pStFileThreadInfo->csPath, GENERIC_WRITE,
FILE_SHARE_READ, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL );
if( hHandle == 0 )
{
REPORT_OPERATION_INFO( _T( "Failed to create the specified file" ));
::SetEvent( pStFileThreadInfo->hFileWriteThreadEvent );
delete pStFileThreadInfo;
pStFileThreadInfo = 0;
CString csAppMsg;
csAppMsg.Format(
_T( "Failed to create the specified file %s, Line num - %d" ),
_T( __FILE__ ), __LINE__);
WriteAppLog( csAppMsg );
return 0;
}
CloseHandle( hHandle );
if( !csReadFile.Open( pStFileThreadInfo->csPath ,
CFile::modeWrite | CFile::typeText ))
{
REPORT_OPERATION_INFO(_T( "Failed to open file" ))
::SetEvent( pStFileThreadInfo->hFileWriteThreadEvent );
delete pStFileThreadInfo;
pStFileThreadInfo = NULL;
CString csAppMsg;
csAppMsg.Format(
_T( "Failed to open file %s, Line num - %d" ),
_T( __FILE__ ), __LINE__);
WriteAppLog( csAppMsg );
return 0;
}
int nListSize = pStFileThreadInfo->csFileInfoArray.GetSize();
for( int nIndex = 0; nIndex < nListSize; ++nIndex )
{
CString csItem = pStFileThreadInfo->csFileInfoArray.GetAt( nIndex );
if( SPACE_CHAR == csItem )
{
continue;
}
int nItemIdx = csItem.Find( EQUAL_CHAR );
if( FAIL != nItemIdx )
{
CString csKey = csItem.Left( nItemIdx );
if( NULL != pStFileThreadInfo->FileInfoMap->Lookup( csKey, stFileInfo ))
{
csReadFile.WriteString( stFileInfo.csFullVariable );
csReadFile.WriteString( _T( "\n" ));
}
else
{
REPORT_OPERATION_INFO( _T( "Unexpected error in file write operation." ));
CString csAppMsg;
csAppMsg.Format(
_T( "Unexpected error in file write operation %s, Line num - %d" ),
_T( __FILE__ ), __LINE__);
WriteAppLog( csAppMsg );
::SetEvent( pStFileThreadInfo->hFileWriteThreadEvent );
delete pStFileThreadInfo;
pStFileThreadInfo = NULL;
return false;
}
}
}
if( NULL != pStFileThreadInfo )
{
::SetEvent( pStFileThreadInfo->hFileWriteThreadEvent );
delete pStFileThreadInfo;
pStFileThreadInfo = NULL;
}
return 1;
}
catch( CMemoryException* pMemoryException )
{
if( NULL != pStFileThreadInfo )
{
delete pStFileThreadInfo;
pStFileThreadInfo = NULL;
}
ExceptionHandler::DumpMFCException( _T( "SaveVariable::FileWriteThreadProc()" ),
pMemoryException, _T( __FILE__ ), __LINE__ );
}
catch( CException* pException )
{
if( NULL != pStFileThreadInfo )
{
delete pStFileThreadInfo;
pStFileThreadInfo = NULL;
}
ExceptionHandler::DumpMFCException( _T( "SaveVariable::FileWriteThreadProc()" ),
pException, _T( __FILE__ ), __LINE__ );
}
catch( ... )
{
if( NULL != pStFileThreadInfo )
{
delete pStFileThreadInfo;
pStFileThreadInfo = NULL;
}
ExceptionHandler::DumpUnknownException( _T( "SaveVariable::FileWriteThreadProc()" ),
_T( __FILE__ ), __LINE__ );
}
return 0;
}
Registry update
“Modifying the registry can cause serious problems that may require you to reinstall your
Operating System. We cannot guarantee that problems resulting from modifications
to the Registry can be solved. Use the information provided at your own risk.”
Environment Variable Compare provides a powerful feature to the user for saving environment variables directly into
the system Registry. I already mentioned that it is a very risky job to update system environment variables. After comparison
the user can update the changed environment variables to the system by using the update system registry option in
the save confirmation dialog. Environment Variable Compare uses a thread for updating system environment variables. To consider safety, during
the save process, Environment Variable Compare skips some of the environment variables permanently. It means any changes in these types of variables during comparison of
the two machines will be shown in the comparison tool but they will not be saved.
The skipped variable names will be listed in the Error trace. The user can check the skipped environment variables from
the Error trace.
The thread which is used to update the system environment variable is RegistryUpdateThreadProc.<code>Refer
shown in the code below.
UINT _cdecl SaveVariable::RegistryUpdateThreadProc( LPVOID pParam_i )
{
REG_THREAD_INFO_t* pstRegThreadInfo = static_cast<REG_THREAD_INFO_t*>( pParam_i );
try
{
if( NULL == pstRegThreadInfo )
{
REPORT_OPERATION_INFO( _T( "Unexpected error. Registry updation failed" ));
CString csAppMsg;
csAppMsg.Format(
_T( "Unexpected error. Registry updation failed %s, Line num - %d" ),
_T( __FILE__ ), __LINE__);
WriteAppLog( csAppMsg );
return 0;
}
FILE_INFO_t stFileinfo;
CString csItem;
CString csKey;
int nFind = 0;
HKEY hEnvVarKey = 0;
CString csNodeKey;
HKEY hRootKey;
csNodeKey = VARIABLE_REGISTER_KEY;
hRootKey = HKEY_LOCAL_MACHINE;
DWORD dwValueType = REG_SZ;
const int MAX_BUFFER_SIZE = 2048;
DWORD dwBufferSize;
dwBufferSize = MAX_BUFFER_SIZE;
CString csEnvironmentVal = _T( "" );
bool bIsAlreadyPresent = false;
int nCount = pstRegThreadInfo->csFileInfoArray.GetSize();
for( int nidx = 0; nidx < nCount; ++nidx )
{
csItem = pstRegThreadInfo->csFileInfoArray.GetAt( nidx );
nFind = csItem.Find( EQUAL_CHAR );
if( FAIL == nCount )
{
continue;
}
csKey = csItem.Left( nFind );
pstRegThreadInfo->FileInfoMap->Lookup( csKey, stFileinfo );
bool isAvailable = false;
for( int nIdx = 0; nIdx < DEF_VAR_COUNT; ++nIdx )
{
if( DefaultVariables[nIdx] == csKey )
{
isAvailable = true;
CString csAppMsg;
csAppMsg.Format(
_T( "Skipped environment variable is %s : %s, Line num - %d" ),
csKey, _T( __FILE__ ), __LINE__);
WriteAppLog( csAppMsg );
break;
}
}
if( !isAvailable )
{
if( ERROR_SUCCESS != ::RegOpenKeyEx( hRootKey , csNodeKey , 0 ,
KEY_QUERY_VALUE , &hEnvVarKey ))
{
REPORT_OPERATION_INFO( _T( "Failed to open registry" ));
CString csAppMsg;
csAppMsg.Format(
_T( "Failed to open registry %s, Line num - %d" ),
_T( __FILE__ ), __LINE__);
WriteAppLog( csAppMsg );
::SetEvent( pstRegThreadInfo->hRegUpdateThreadEvent );
delete pstRegThreadInfo;
pstRegThreadInfo = 0;
return 0;
}
TCHAR* ptszBuffer = 0;
dwValueType = REG_NONE;
bIsAlreadyPresent = false;
ptszBuffer = new TCHAR[MAX_BUFFER_SIZE];
ZeroMemory( ptszBuffer, dwBufferSize );
if( ERROR_SUCCESS == ::RegQueryValueEx( hEnvVarKey , csKey ,
0 , &dwValueType , reinterpret_cast <LPBYTE>( ptszBuffer ), &dwBufferSize ))
{
OutputDebugString( _T( "Check:" )+ csKey +_T( ":" )+stFileinfo.csVaribleValue );
bIsAlreadyPresent = true;
}
RegCloseKey( hEnvVarKey );
delete []ptszBuffer;
ptszBuffer = 0;
DWORD dwRegType;
if( ERROR_SUCCESS == ::RegOpenKeyEx( hRootKey , csNodeKey , 0 ,
KEY_SET_VALUE , &hEnvVarKey ))
{
if( bIsAlreadyPresent )
{
if( 0 == dwValueType )
{
dwRegType = REG_NONE;
}
else if( UNITY == dwValueType )
{
dwRegType = REG_SZ;
}
else if( 2 == dwValueType )
{
dwRegType = REG_EXPAND_SZ;
}
else
{
}
}
else
{
dwRegType = REG_SZ;
}
}
csEnvironmentVal = stFileinfo.csVaribleValue;
dwBufferSize = 0;
dwBufferSize = csEnvironmentVal.GetLength()*sizeof( TCHAR );
LPSTR lpszBuffer = reinterpret_cast<LPSTR>( csEnvironmentVal.GetBuffer( dwBufferSize ));
if( ERROR_SUCCESS == ::RegSetValueEx( hEnvVarKey , csKey, 0 , dwRegType ,
reinterpret_cast<LPBYTE>( lpszBuffer ) ,dwBufferSize ))
{
}
csEnvironmentVal.ReleaseBuffer();
RegCloseKey( hEnvVarKey );
}
else
{
}
}
if( NULL != pstRegThreadInfo )
{
::SetEvent( pstRegThreadInfo->hRegUpdateThreadEvent );
delete pstRegThreadInfo;
pstRegThreadInfo = NULL;
}
return 1;
}
catch( CMemoryException* pMemoryException )
{
if( NULL != pstRegThreadInfo )
{
delete pstRegThreadInfo;
pstRegThreadInfo = NULL;
}
ExceptionHandler::DumpMFCException( _T( "SaveVariable::RegistryUpdateThreadProc()" ),
pMemoryException, _T( __FILE__ ), __LINE__ );
}
catch( CException* pException )
{
if( NULL != pstRegThreadInfo )
{
delete pstRegThreadInfo;
pstRegThreadInfo = NULL;
}
ExceptionHandler::DumpMFCException( _T( "SaveVariable::RegistryUpdateThreadProc()" ),
pException, _T( __FILE__ ), __LINE__ );
}
catch( ... )
{
if( NULL != pstRegThreadInfo )
{
delete pstRegThreadInfo;
pstRegThreadInfo = NULL;
}
ExceptionHandler::DumpUnknownException( _T( "SaveVariable::RegistryUpdateThreadProc()" ),
_T( __FILE__ ), __LINE__ );
}
return 0;
}
5. Info Listener
This is a very powerful dynamic error reporting technique used in
Environment Variable Compare for reporting any runtime execution information to
the user. Actually Info Listener is a separate C++ application. This application is run on
the system tray when the user turns on the “Execute Info Listener” option from
the Environment Variable Compare options. Please refer to the figure below which shows the style of error reporting.
To communicate between two
C++ applications there is
a Windows message called WM_COPYDATA
. Please refer to the code below.
void CEnvironmentVarCompareView::OnExecuteListner()
{
try
{
CString csFileName;
csFileName = _T( "\\" ) + csInfoListner;
CWnd* hWnd = FindWindow( NULL, csInfoListner );
if( NULL != hWnd )
{
REPORT_OPERATION_INFO( _T( "Hey iam here..." ));
return;
}
TCHAR szDirectorPath[MAX_PATH];
GetModuleFileName( NULL, szDirectorPath, MAX_PATH );
CString csDirectoryPath = szDirectorPath;
int nPos = csDirectoryPath.ReverseFind( _T( '\\' ));
if( FAIL != nPos )
{
CString csDir = csDirectoryPath.Left( nPos );
csDir += csFileName;
SHELLEXECUTEINFO ExecuteInfo;
memset( &ExecuteInfo, 0, sizeof(ExecuteInfo));
ExecuteInfo.cbSize = sizeof(ExecuteInfo);
ExecuteInfo.fMask = 0;
ExecuteInfo.hwnd = 0;
ExecuteInfo.lpVerb = _T( "open" ); ExecuteInfo.lpFile = csDir; ExecuteInfo.lpDirectory = 0; ExecuteInfo.nShow = SW_SHOW;
ExecuteInfo.hInstApp = 0;
if( ShellExecuteEx( &ExecuteInfo ) == FALSE )
{
if( IDYES == AfxMessageBox(
_T( "Do you want browse application?" ),
MB_ICONQUESTION|MB_YESNO ))
{
CFileDialog FilDlgObj( TRUE );
CString csFilePath; UpdateData( TRUE );
if( IDOK == FilDlgObj.DoModal())
{
csFilePath = FilDlgObj.GetPathName();
int nPosition = csFilePath.ReverseFind('\\');
if( FAIL != nPosition )
{
CString csFileName =
csFilePath.Right( csFilePath.GetLength() - nPosition - UNITY );
if( 0 != csFileName.Compare( _T( "InfoListner.exe" ) ))
{
AfxMessageBox( _T( "Sorry. Selected application is not InfoListner" ));
CString csAppMsg;
csAppMsg.Format(_T( "Sorry. Selected application is not "
"InfoListner - %s, Line num - %d" ), _T( __FILE__ ), __LINE__);
WriteAppLog( csAppMsg );
return;
}
}
SHELLEXECUTEINFO ExecuteInfoEx;
memset( &ExecuteInfoEx, 0, sizeof(ExecuteInfoEx));
ExecuteInfoEx.cbSize = sizeof(ExecuteInfoEx);
ExecuteInfoEx.fMask = 0;
ExecuteInfoEx.hwnd = 0;
ExecuteInfoEx.lpVerb = _T( "open" ); ExecuteInfoEx.lpFile = csFilePath; ExecuteInfoEx.lpDirectory = 0; ExecuteInfoEx.nShow = SW_SHOW;
ExecuteInfoEx.hInstApp = 0;
if( ShellExecuteEx( &ExecuteInfoEx ) == FALSE )
{
}
}
}
}
}
}
catch( CException* pException )
{
ExceptionHandler::DumpMFCException(
_T( "CEnvironmentVarCompareView::OnExecuteListner()" ),
pException, _T( __FILE__ ), __LINE__ );
}
catch( ... )
{
ExceptionHandler::DumpUnknownException(
_T( "CEnvironmentVarCompareView::OnExecuteListner()" ),
_T( __FILE__ ), __LINE__ );
}
}
Two receive messages from Environment Variable Compare, the Info Listener application should handle the WM_COPYDATA
message.
LRESULT CMainFrame::OnRecieve( WPARAM wParam_i, LPARAM lParam_i )
{
PCOPYDATASTRUCT pstPageInfo = reinterpret_cast<PCOPYDATASTRUCT>( lParam_i );
int nDataLength = pstPageInfo->cbData / sizeof( TCHAR );
TCHAR *ptcInfoMsg = 0;
ptcInfoMsg = new TCHAR[pstPageInfo->cbData + 1];
memset( ptcInfoMsg, 0, pstPageInfo->cbData );
memset( ptcInfoMsg, 0, pstPageInfo->cbData );
_tcsnccpy( ptcInfoMsg,
reinterpret_cast<wchar_t *>( pstPageInfo->lpData ), nDataLength );
ptcInfoMsg[nDataLength] = 0;
CView* pView = GetActiveView();
CString csText;
if( 0 == pView )
{
return 0;
}
pView->PostMessage( INFO_MSG, 0, reinterpret_cast<LPARAM>( ptcInfoMsg ));
return 1;
}
Suppose the Info Listener application is already running on the system tray, then
the user can not run another instance of Info Listener using the Environment Variable
Compare application.
6. Path Editor
Editing the PATH environment variable in Windows is an unpleasant experience. First, it takes several steps to get to the interface. Second, the interface is impossible: Something you can do to make the task easier is copy the whole field, edit it in a text editor, and paste it back.
But Environment Variable Compare path editor provides a better experience for users. It provides all the features to
the user for editing environment variables in better ways.
7.Compare files through Command prompt
This is an important feature provided to user for comparing their environment variables through
a command prompt. The execution step is explained below.
- Run command prompt.
- Change directory to your application exe path using “cd” command.
- Enter exe name and give a space and enter path of files you want to compare (e.g.:
EnvironmentVarCompare.exe C:\FileFirst.txt C:\FileSecond.txt).
- Press Enter button from keyboard.
Expected result: EnvironmentVariableCompare will be run and shows the compared results in two sides.
[You can check the code from the attached files.]
To parse the command line at start up, there is
a Windows class called CCommandLineInfo
.
Remarks: To get the command line parameters we need to create a class from
CCommandLineInfo
. Please check the code below.
class ReadCommandLineInfo : public CCommandLineInfo
{
public:
ReadCommandLineInfo(void);
~ReadCommandLineInfo(void);
protected:
virtual void ParseParam( LPCTSTR, BOOL bFlag_i, BOOL bLast_i );
private:
CStringArray m_csParamArray;
};
After getting the first parameter, we need to keep that parameter in our internal data structure. This is because
we get the parameters from the command line one by one. So after getting the first parameter, internally a timer will be started. When
the timer elapses it starts to compare the variables and shows the result. To perform the command line comparison, Environment
Variable Compare uses a class called PerformCmdLineCompare.
#define THREAD_CMD_INFO WM_USER + 1001
IMPLEMENT_DYNCREATE(PerformCmdLineCompare, CWinThread)
PerformCmdLineCompare* PerformCmdLineCompare::m_pPerfmCmdLineOp = 0;
UINT_PTR PerformCmdLineCompare::m_uPerformCmdOptimerID = 0;
int PerformCmdLineCompare::m_nDelayTime = 0;
PerformCmdLineCompare::PerformCmdLineCompare()
{
}
PerformCmdLineCompare::~PerformCmdLineCompare()
{
}
BOOL PerformCmdLineCompare::InitInstance()
{
return TRUE;
}
int PerformCmdLineCompare::ExitInstance()
{
return CWinThread::ExitInstance();
}
BEGIN_MESSAGE_MAP(PerformCmdLineCompare, CWinThread)
ON_THREAD_MESSAGE(WM_TIMER, OnTimer )
ON_THREAD_MESSAGE(THREAD_CMD_INFO, OnReciveCmd )
END_MESSAGE_MAP()
PerformCmdLineCompare* PerformCmdLineCompare::GetInstance()
{
try
{
if( NULL == m_pPerfmCmdLineOp )
{
m_pPerfmCmdLineOp = dynamic_cast<PerformCmdLineCompare*>(
::AfxBeginThread( RUNTIME_CLASS( PerformCmdLineCompare )));
if( NULL == m_pPerfmCmdLineOp )
{
AfxMessageBox( _T( "Failed to create object" ));
REPORT_OPERATION_INFO( _T( "Unable to create PerformCmdLineCompare." ))
}
m_nDelayTime = 200;
}
}
catch( CMemoryException* pMemoryException )
{
ExceptionHandler::DumpMFCException( _T( "PerformCmdLineCompare::GetInstance()" ),
pMemoryException, _T( __FILE__ ), __LINE__ );
}
catch( CException* pException )
{
ExceptionHandler::DumpMFCException( _T( "PerformCmdLineCompare::GetInstance()" ),
pException, _T( __FILE__ ), __LINE__ );
}
catch( ... )
{
ExceptionHandler::DumpUnknownException( _T( "PerformCmdLineCompare::GetInstance()" ),
_T( __FILE__ ), __LINE__ );
}
return m_pPerfmCmdLineOp;
}
void PerformCmdLineCompare::OnTimer( UINT nIDEvent_i, LPARAM lparam_i )
{
try
{
::KillTimer( NULL, m_uPerformCmdOptimerID );
m_uPerformCmdOptimerID = 0;
if( 2 < m_csParamInfoArray.GetSize())
{
REPORT_OPERATION_INFO( _T( "Invalied parameter format" ));
return;
}
CEnvironmentVarCompareView* pMainView =
PooledInstance::GetInstance().GetMainViewPtr();
if( 0 != pMainView )
{
CString csLeftFile = m_csParamInfoArray.GetAt( 0 );
CString csRightFile = m_csParamInfoArray.GetAt( 1 );
pMainView->ReadCmdLineFiles( csLeftFile, csRightFile );
}
}
catch( CException* pException )
{
ExceptionHandler::DumpMFCException( _T( "PerformCmdLineCompare::OnTimer()" ),
pException, _T( __FILE__ ), __LINE__ );
}
catch( ... )
{
ExceptionHandler::DumpUnknownException( _T( "PerformCmdLineCompare::OnTimer()"),
_T( __FILE__ ), __LINE__ );
}
}
void PerformCmdLineCompare::ReciveParam( CStringArray& csParamInfoArray_i )
{
try
{
if( 0 != m_uPerformCmdOptimerID )
{
REPORT_OPERATION_INFO( _T( "Another comparison operation in progress."));
CString csAppMsg;
csAppMsg.Format(_T( "Another comparison operation in"
" progress. - %s, Line num - %d" ), _T( __FILE__ ), __LINE__);
WriteAppLog( csAppMsg );
return;
}
m_csParamInfoArray.RemoveAll();
m_csParamInfoArray.Copy( csParamInfoArray_i );
m_pPerfmCmdLineOp->PostThreadMessage( THREAD_CMD_INFO, NULL, NULL );
}
catch( CException* pException )
{
ExceptionHandler::DumpMFCException( _T( "PerformCmdLineCompare::ReciveParam()"),
pException, _T( __FILE__ ), __LINE__ );
}
catch( ... )
{
ExceptionHandler::DumpUnknownException( _T( "PerformCmdLineCompare::ReciveParam()" ),
_T( __FILE__ ), __LINE__ );
}
}
void PerformCmdLineCompare::OnReciveCmd( WPARAM wParam_i, LPARAM lParam_i )
{
try
{
m_uPerformCmdOptimerID = ::SetTimer( NULL, 0, 1000, NULL );
if( 0 == m_uPerformCmdOptimerID )
{
REPORT_OPERATION_INFO( _T( "Timer not started." ));
}
}
catch( CException* pException )
{
ExceptionHandler::DumpMFCException( _T( "PerformCmdLineCompare::OnReciveCmd()" ),
pException, _T( __FILE__ ), __LINE__ );
}
catch( ... )
{
ExceptionHandler::DumpUnknownException( _T( "PerformCmdLineCompare::OnReciveCmd()" ),
_T( __FILE__ ), __LINE__ );
}
}
8. Auto complete in combo box
You have probably noticed that when we type some paths in the Run dialog of Windows, it will list the files and folders under that path. Similarly, when you type a path in the combobox of this application, it will list the files and folders under that path. SHAutoComplete
takes care of this feature. This function needs a handle of the edit control.
CoInitialize(0);
AfxOleInit();
COMBOBOXINFO info = { sizeof(COMBOBOXINFO) };
BOOL bLeftComboInfo = GetComboBoxInfo( m_LeftCombo.m_hWnd, &info );
if( bLeftComboInfo )
{
HRESULT hRes = SHAutoComplete( info.hwndItem, SHACF_FILESYSTEM );
}
9. Quick compare info
This feature helps the user to find quick comparison information in very less time, i.e., after comparison we need to know how many different lines are present and how many new lines are present in
the compared file. One solution for this problem is to count the lines manually. But it requires much more time to find. So in this tool I introduced a new interface for it. Please find the option “Compare Info” from
the File option. It provides three main information for the user. They are:
- Number of same lines
- Number of different lines
- Number of new lines
Please refer to the figure below.
Add New variables
Environment variable compare provide an option to user for adding new variables to system. You can add new variables to system or user variables by selecting appropriate option from specified combo box.
System Variables
You must be an administrator to modify a system environment variable. So when you want to add new variables to your system, you should run this application with Administrator privilege. System environment variables are defined by Windows and apply to all computer users. Changes to the system environment are written to the registry, and usually require a restart to become effective.
User Variable for User Name
Any user can add, modify, or remove a user environment variable. Environment variable compare provide an option to add new variable to system user variables. The changes are written to the registry, and are usually effective immediately. However, after a change to user environment variables is made, any open software programs should be restarted to force them to read the new registry values. The common reason to add variables is to provide data that is required for variables that you want to use in scripts.
Revision History
- 07-July-2013 :- First post.
- 26-July-2013 : Given explanation for Adding new variables to system.