Click here to Skip to main content
Click here to Skip to main content

Issue Tracker

, 14 Jul 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Did you spot any bugs today? Than this tool might save some of your time

Notes: the MySQL script must be run before the application is launched!

Introduction

Recently I found a great Bug Tracking Tool. But, before I use that one in production mode, I found that there are a few things missing from it like:

  • ability to send notification e-mails (if a solution was found)
  • ability to attach files (of any kind), such as screenshots, etc.
  • ability to log the interaction between bugs (dependencies)
  • developed as a standalone Windows-based binary application

 

A really good alternative would be the ToDoList and BugReporter projects for some of my tasks; but I needed a more scalable, distributed solution ...

Using the Software

Did you run the MySQL script for database creation? Then it is time to establish your first connection to the database. You have to enter the Server name, Database name, username and password access to database, as you can see below: (please make sure you have the proper rights to apply INSERT, UPDATE and DELETE statements on the Issue Tracker's database tables)

Every time the Issue Tracker application starts, it checks for the current logged in user, using the GetUserNameEx function (and then with GetUserName function if the first one fails) and adds it to the database if it is not there already. I assume that the computers using this software are on a private network using Active Directory authentication, which is the case for most software companies.

Next, you should choose your settings using the Settings... option from Issue Tracker's system menu:

And now you are ready to use the Issue Tracker application.

  • to add a new bug click New, fill in the information, then click Insert; to abort the operation hit the Cancel button.
  • to modify the data of a bug select it from the top list, click Change, update the information, than click Update; to abort the operation hit the Cancel button.
  • to save the bug's information into a HTML document, select it from the top list, click Save as ..., then choose a file name, and hit OK.
  • to print the bug's information, select it from the top list, click Print ..., than select your printer, and hit OK.
  • to search the database by specific fields, unselect the issues from the top list, fill in the search criteria, and than hit Search.

 

The implementation

The Issue Tracker application has been designed with three layers of abstraction in mind (so that this software can be easily extended):

  • the MFC/application layer - provides the basic functions of a typical Windows application; this layer it is implemented into the CIssueTrackerApp, CIssueTrackerDlg, CUserConfigurationDlg, CFileAttachementsDlg, CConnectionSettingsDlg and CSmtpConfigurationDlg classes;
  • the object/data manipulation layer - provides specific functions to insert / delete / search for users, issues, and for its associated files and notes; it is implemented into the CIssueUserItem, CIssueUserList, CIssueFileItem, CIssueFileList, CIssueNoteItem, CIssueNoteList, CIssueDataItem and CIssueDataListcode classes;
  • the ODBC database access layer - provides wrapper functions for database access through ODBC API; it is implemented into the CDatabaseConnector and CDatabaseRecordset classes.

 

There is not really much to be said about the classes from the MFC/application layer:

  • the CIssueTrackerDlg class is the main interface of the application;
  • the CUserConfigurationDlg class implements the user configuration dialog;
  • the CFileAttachementsDlg class allows the user to perform basic operations on the files attached on a bug;
  • the CConnectionSettingsDlg class allows the user to set-up the connection to database;
  • the CSmtpConfigurationDlg class allows the user to set-up the connection to SMTP server.

 

The CIssueUserItem class is a wrapper around the IssueUser table and it has the following member functions:

  • UINT GetID() - returns the ID of an user;
  • SetID( UINT uintID ) - sets the ID of an user;
  • CString GetFullName() - returns the full name of an user;
  • void SetFullName( CString lpszFullName ) - sets the full name of an user;
  • CString GetEmailAddr() - returns the e-mail address of an user;
  • void SetEmailAddr( CString lpszEmailAddr ) - sets the e-mail address of an user;
  • GetNotification() - returns TRUE if a user wants notification through e-mail, and FALSE otherwise;
  • void SetNotification( UINT uintNotification ) - enables/disables e-mail notification for an user;
  • void CopyDataFrom( CIssueUserItem * pIssueUser ) - makes a copy of the data of another object of the same type.

 

The CIssueUserList class is an array of CIssueUserItem objects and it has the following member functions:

  • int GetSize() - returns the count of the users currently available;
  • CIssueUserItem * GetItemAt( int nItemIndex ) - returns the user from nItemIndex position in array;
  • void SetItemAt( int nItemIndex, CIssueUserItem * pIssueUser ) - sets a new user to nItemIndex position in array;
  • void RemoveAllItems() - deletes all users stored in array;
  • BOOL LoadAllItems() - load all users from database;
  • BOOL InsertItem( CIssueUserItem * pIssueUser ) - inserts one new user into array, and also into the database;
  • BOOL UpdateItem( CIssueUserItem * pIssueUser ) - updates the data of an user from array, and also into the database;
  • BOOL DeleteItem( CIssueUserItem * pIssueUser ) - deletes an user from array, and also from the database;
  • CIssueUserItem * SearchItem( UINT uintID, BOOL bUseDatabase ) - search for an user into array/database, based on his/her ID;
  • CIssueUserItem * SearchItem( CString lpszFullName, BOOL bUseDatabase ) - search for an user into array/database, based on his/her full name;
  • CIssueUserItem * SearchAddr( CString lpszEmailAddr, BOOL bUseDatabase ) - search for an user into array/database, based on his/her e-mail address;
  • UINT GetUserLoggedID() - returns the ID of the currently logged in user;
  • void SetUserLoggedID( UINT uintID ) - sets the ID of the currently logged in user;
  • CString GetUserLoggedName() - returns the full name of the currently logged in user;
  • void SetUserLoggedName( CString lpszName ) - sets the full name of the currently logged in user;
  • CString GetUserLoggedEmailAddr() - returns the e-mail address of the currently logged in user;
  • void SetUserLoggedEmailAddr( CString lpszEmailAddr ) - sets the e-mail address of the currently logged in user;
  • BOOL ValidateCurrentUserLogged() - adds the currently logged in user to the database if its not there.

 

The CIssueFileItem class is a wrapper around the IssueFile table and it has the following member functions:

  • UINT GetID() - returns the ID of the file;
  • void SetID( UINT uintID ) - sets the ID of the file;
  • UINT GetIssueID() - returns the ID of an issue associated with the file;
  • void SetIssueID( UINT uintIssueID ) - sets the ID of an issue associated with the file;
  • CIssueUserItem * GetAuthor() - returns the author of the file;
  • void SetAuthor( CIssueUserItem * itemAuthor ) - sets the author of the file;
  • CString GetFileName() - returns the name of the file;
  • void SetFileName( CString lpszFileName ) - sets the name of the file;
  • UINT GetFileSize() - returns the size in bytes of the file;
  • void SetFileSize( UINT uintFileSize ) - sets the size in bytes of the file;
  • CTime GetModified() - returns the date/time of the file;
  • void SetModified( CTime timeModified ) - sets the date/time of the file;
  • CLongBinary * GetContent() - gets the content of the file;
  • void SetContent( CLongBinary * byteContent ) - set the content of the file;
  • void CopyDataFrom( CIssueFileItem* pIssueFile ) - makes a copy of the data of another object of the same type.

 

The CIssueFileList class is an array of CIssueFileItem objects and it has the following member functions:

  • int GetSize() - returns the count of the files currently available for an issue;
  • CIssueFileItem * GetItemAt( int nItemIndex ) - returns the file from nItemIndex position in array;
  • void SetItemAt( int nItemIndex, CIssueFileItem * pIssueFile ) - sets a new file to nItemIndex position in array;
  • void RemoveAllItems() - deletes all files stored in array;
  • BOOL LoadAllItems( UINT uintIssueID, CIssueUserList * listIssueUser ) - load all files from database associated to an issue;
  • BOOL InsertItem( CIssueFileItem * pIssueFile ) - inserts one new file into array, and also into the database;
  • BOOL UpdateItem( CIssueFileItem * pIssueFile ) - updates the data of a file from array, and also into the database;
  • BOOL DeleteItem( CIssueFileItem * pIssueFile ) - deletes a file from array, and also from the database;
  • CIssueFileItem * SearchItem( UINT uintID, CIssueUserList * listIssueUser, BOOL bUseDatabase ) - search for a file into array/database, based on its ID.

 

The CIssueNoteItem class is a wrapper around the IssueNote table and it has the following member functions:

  • UINT GetID() - returns the ID of the note;
  • void SetID( UINT uintID ) - sets the ID of the note;
  • UINT GetIssueID() - returns the ID of an issue associated with the note;
  • void SetIssueID( UINT uintIssueID ) - sets the ID of an issue associated with the note;
  • CIssueUserItem * GetAuthor() - returns the author of the note;
  • void SetAuthor( CIssueUserItem * itemAuthor ) - sets the author of the note;
  • UINT GetFileSize() - returns the size in bytes of the note;
  • void SetFileSize( UINT uintFileSize ) - sets the size in bytes of the note;
  • CTime GetModified() - returns the date/time of the note;
  • void SetModified( CTime timeModified ) - sets the date/time of the note;
  • CLongBinary * GetContent() - gets the content of the note;
  • void SetContent( CLongBinary * byteContent ) - set the content of the note;
  • void CopyDataFrom( CIssueNoteItem* pIssueNote ) - makes a copy of the data of another object of the same type.

 

The CIssueNoteList class is an array of CIssueNoteItem objects and it has the following member functions:

  • int GetSize() - returns the count of the notes currently available for an issue;
  • CIssueNoteItem * GetItemAt( int nItemIndex ) - returns the note from nItemIndex position in array;
  • void SetItemAt( int nItemIndex, CIssueNoteItem * pIssueNote ) - sets a new note to nItemIndex position in array;
  • void RemoveAllItems() - deletes all notes stored in array;
  • BOOL LoadAllItems( UINT uintIssueID, CIssueUserList * listIssueUser ) - load all notes from database associated to an issue;
  • BOOL InsertItem( CIssueNoteItem * pIssueNote ) - inserts one new note into array, and also into the database;
  • BOOL UpdateItem( CIssueNoteItem * pIssueNote ) - updates the data of a note from array, and also into the database;
  • BOOL DeleteItem( CIssueNoteItem * pIssueNote ) - deletes a note from array, and also from the database;
  • CIssueNoteItem * SearchItem( UINT uintID, CIssueUserList * listIssueUser, BOOL bUseDatabase ) - search for a note into array/database, based on its ID.

 

The CIssueDataItem class is a wrapper around the IssueData table and it has the following member functions:

  • UINT GetID() - returns the ID of an issue;
  • void SetID( UINT uintID ) - sets the ID of an issue;
  • CString GetTitle() - returns the title of an issue;
  • void SetTitle( CString lpszTitle ) - sets the title of an issue;
  • CString GetProduct() - returns the product where the issue was generated;
  • void SetProduct( CString lpszProduct ) - sets the product where the issue was generated;
  • CString GetComponent() - returns the component where the issue was generated;
  • void SetComponent( CString lpszComponent ) - sets the component where the issue was generated;
  • CString GetVersion() - returns the version of software witch generated the issue;
  • void SetVersion( CString lpszVersion ) - sets the version of software witch generated the issue;
  • CString GetPlatform() - returns the platform of software where the issue was generated;
  • void SetPlatform( CString lpszPlatform ) - sets the platform of software where the issue was generated;
  • UINT GetStatus() - sets the status of the issue;
  • void SetStatus( UINT uintStatus ) - returns the status of the issue;
  • UINT GetImportance() - returns the priority of the issue;
  • void SetImportance( UINT uintImportance ) - sets the priority of the issue;
  • CString GetMilestone() - returns the deadline of the issue;
  • void SetMilestone( CString lpszMilestone ) - sets the deadline of the issue;
  • CIssueUserItem * GetReporter() - returns the user who reported this issue;
  • SetReporter( CIssueUserItem * itemReporter ) - sets the user who reported this issue;
  • CIssueUserItem * GetResolver() - returns the software developer who should find a fix for this issue;
  • void SetResolver( CIssueUserItem * itemResolver ) - sets the software developer who should find a fix for this issue;
  • CIssueUserItem * GetConfirmer() - returns the QA contact who would confirm the fix for this issue;
  • void SetConfirmer( CIssueUserItem * itemConfirmer ) - sets the QA contact who would confirm the fix for this issue;
  • CString GetDependencies() - returns the IDs of the issues on witch this one depends;
  • void SetDependencies( CString lpszDependencies ) - sets the IDs of the issues on witch this one depends;
  • CString GetBlocksIssues() - returns the IDs of the issues blocked by this issue;
  • void SetBlocksIssues( CString lpszBlocksIssues ) - sets the IDs of the issues blocked by this issue;
  • CString GetDistribution() - returns the CC list of e-mail addresses of the notification e-mail;
  • void SetDistribution( CString lpszDistribution ) - sets the CC list of e-mail addresses of the notification e-mail;
  • CTime GetModified() - returns the date/time when this issue was last modified;
  • void SetModified( CTime timeModified ) - sets the date/time when this issue was last modified;
  • void CopyDataFrom( CIssueDataItem* pIssueData ) - makes a copy of the data of another object of the same type.

 

The CIssueDataList class is an array of CIssueDataItem objects and it has the following member functions:

  • int GetSize() - returns the count of the issues currently available;
  • CIssueDataItem * GetItemAt( int nItemIndex ) - returns the issue from nItemIndex position in array;
  • void SetItemAt( int nItemIndex, CIssueDataItem * pIssueData ) - sets a new issue to nItemIndex position in array;
  • void RemoveAllItems() - deletes all issues stored in array;
  • BOOL LoadAllItems( BOOL bRefreshUsers, BOOL bLoadAllFiles, BOOL bLoadAllNotes, BOOL bFindRelations ) - load all issues from database;
  • BOOL InsertItem( CIssueDataItem * pIssueData ) - inserts one new issue into array, and also into the database;
  • BOOL UpdateItem( CIssueDataItem * pIssueData ) - updates the data of an issue from array, and also into the database;
  • BOOL DeleteItem( CIssueDataItem * pIssueData ) - deletes an issue from array, and also from the database;
  • CIssueNoteItem * SearchItem( UINT uintID, BOOL bUseDatabase ) - search for an issue into array/database, based on its ID;
  • BOOL SearchEngine( CIssueDataItem * pIssueData, BOOL bRefreshUsers, BOOL bLoadAllFiles, BOOL bLoadAllNotes, BOOL bFindRelations ) - search for all issues that matches the fields specified in pIssueData;
  • BOOL FindRelation( UINT uintID, CString &lpszBlocks ) - search for all IDs of the issues that this issue blocks.

 

The CDatabaseConnector class is the basic resource to access our database through ODBC driver and it has the following member functions:

  • void AllocConnectorHandle() - allocates a new handle for a database connection;
  • void FreeConnectorHandle() - frees the handle allocated before for a database connection;
  • BOOL DriverConnect( LPCTSTR lpszInputConnection ) - creates a new ODBC connection to the database;
  • BOOL OpenConnection() - opens a new ODBC connection to the database;
  • void CloseConnection() - close the current ODBC connection to the database;
  • BOOL ExecuteStatement( LPCTSTR lpszStatement ) - executes an SQL statement (such an INSERT, UPDATE, DELETE);
  • void SetLoginTimeout( const long nLoginTimeout ) - sets the timeout for establishing a connection to the database;
  • long GetLoginTimeout() - returns the timeout for establishing a connection to the database;
  • BOOL IsDatabaseOpened() - returns TRUE if a connection to the database was established, and FALSE otherwise;
  • long GetRowsAffected() - returns the number of the rows affected by the last SQL interrogation executed;
  • DATABASE_TYPE GetDatabaseType() - returns the database type of the current connection (e.g. SQL SERVER, ORACLE, MYSQL etc.);
  • void SetDatabaseType( DATABASE_TYPE uintDatabaseType ) - sets the database type for a new connection.

 

The CDatabaseRecordset class makes use of the connection provided by CDatabaseConnector and it has the following member functions:

  • void AllocRecordsetHandle() - allocates a new handle for an SQL statement;
  • void FreeRecordsetHandle() - frees the handle allocated before for an SQL statement;
  • BOOL OpenRecordset( LPCTSTR lpszStatement ) - executes an SQL interrogation and fetch the row set of data;
  • void CloseRecordset() - close the row set of data created by the previous SQL interrogation;
  • void GetFieldValue( WORD nFieldIndex, LONG nFieldType, DWORD nFieldSize, void* pDataBuffer ) - fetches a value from the nFieldIndex column, of type nFieldType, and it stores to pDataBuffer of size given by nFieldSize;
  • void GetColumnAttr( WORD nColumnIndex, LPTSTR lpszColumnName, WORD nBufferLength, WORD &nColumnType, ULONG &nColumnSize, WORD &nColumnScale, BOOL &bNullable ) - gets the attributes of a column from the current row set of data;
  • void SeekFirstRecord() - moves the cursor to the first record of the current row set of data;
  • void SeekLastRecord() - moves the cursor to the last record of the current row set of data;
  • void SeekPrevRecord() - moves the cursor to the previous record of the current row set of data;
  • void SeekNextRecord() - moves the cursor to the next record of the current row set of data;
  • long GetNumResultRows() - returns the number of the rows affected by the last SQL interrogation executed;
  • long GetNumResultCols() - returns the number of columns in the current row set of data;
  • void SetQueryTimeout( const long nQueryTimeout ) - sets the timeout for a SQL interrogation;
  • long GetQueryTimeout() - returns the timeout selected for a SQL interrogation;
  • BOOL IsRecordsetOpened() - returns TRUE if a row set of data is available, and FALSE otherwise;
  • BOOL IsRecordsetBOF() - returns TRUE if the cursor reached the begin of row set of data, and FALSE otherwise;
  • BOOL IsRecordsetEOF() - returns TRUE if the cursor reached the end of row set of data, and FALSE otherwise.

 

Points of interest

It is well known that Windows provides an easy access to its Registry database. So the Issue Tracker application makes a good use of it to store the database access settings. But I just could not store the access password in plain text. So I looked at the Microsoft Cryptography Library and I decided to implement two helper functions for this purpose: GetRegistryPassword and SetRegistryPassword.

/// Returns a decrypted password read from Windows Registry, using Crypto API calls
BOOL GetRegistryPassword( LPCTSTR lpszCryptoKey, LPCTSTR lpszSection, LPCTSTR lpszEntry, LPTSTR lpszValue, LPCTSTR lpszDefault )
{
	if ( !lpszSection || !lpszEntry || !lpszValue )
		return FALSE;

	if ( !USE_CRYPTO_METHODS )
	{
		return ( _tcscpy( lpszValue, AfxGetApp()->GetProfileString( lpszSection, lpszEntry, lpszDefault ) ) != NULL);
	}

	if ( !lpszCryptoKey )
		lpszCryptoKey = AfxGetAppName();

	LPBYTE lpcbPassword = (LPBYTE) lpszCryptoKey;
	const DWORD dwPasswordLen = (DWORD) ( sizeof(TCHAR) * _tcslen( lpszCryptoKey ) );

	BYTE lpcbDataValue[PASSWORD_MAXLENGTH];
	DWORD dwHowManyBytes = 0;
	LPBYTE lpcbTempBuffer = NULL;

	BOOL bDecryptionDone = FALSE;
	HCRYPTPROV hCryptoProvider = NULL;
	HCRYPTHASH hCryptoHash = NULL;
	HCRYPTKEY hCryptoKey = NULL;

	if ( AfxGetApp()->GetProfileBinary( lpszSection, lpszEntry, (LPBYTE *)&lpcbTempBuffer, (UINT *)&dwHowManyBytes ) )
	{
		if ( dwHowManyBytes != 0)
		{
			ZeroMemory( lpcbDataValue, sizeof( lpcbDataValue ) );
			CopyMemory( lpcbDataValue, lpcbTempBuffer, dwHowManyBytes );

			if ( CryptAcquireContext( &hCryptoProvider, NULL, NULL, PROV_RSA_FULL, 0 ) )
			{
				if ( CryptCreateHash( hCryptoProvider, CALG_MD5, NULL, 0, &hCryptoHash ) )
				{
					if ( CryptHashData( hCryptoHash, lpcbPassword, dwPasswordLen, 0 ) )
					{
						if ( CryptDeriveKey( hCryptoProvider, CALG_RC4, hCryptoHash, CRYPT_EXPORTABLE, &hCryptoKey ) )
						{
							if ( CryptDecrypt( hCryptoKey, NULL, TRUE, 0, lpcbDataValue, &dwHowManyBytes ) )
							{
								bDecryptionDone = TRUE;
								_tcscpy( lpszValue, (LPTSTR) lpcbDataValue );
							}
							else
							{
								DisplayLastError( _T("CryptDecrypt: ") );
							}
							VERIFY( CryptDestroyKey( hCryptoKey ) );
						}
						else
						{
							DisplayLastError( _T("CryptDeriveKey: ") );
						}
					}
					else
					{
						DisplayLastError( _T("CryptHashData: ") );
					}
					VERIFY( CryptDestroyHash( hCryptoHash ) );
				}
				else
				{
					DisplayLastError( _T("CryptCreateHash: ") );
				}
				VERIFY( CryptReleaseContext( hCryptoProvider, 0 ) );
			}
			else
			{
				DisplayLastError( _T("CryptAcquireContext: ") );
			}
		}
	}
	else
	{
		if ( !dwHowManyBytes )
		{
			_tcscpy( lpszValue, lpszDefault );
			bDecryptionDone = TRUE;
		}
	}

	if ( lpcbTempBuffer != NULL )
		delete lpcbTempBuffer;

	return bDecryptionDone;
}

and

/// Writes an encrypted password to Windows Registry, using Crypto API calls
BOOL SetRegistryPassword( LPCTSTR lpszCryptoKey, LPCTSTR lpszSection, LPCTSTR lpszEntry, LPTSTR lpszValue )
{
	if ( !lpszSection || !lpszEntry || !lpszValue )
		return FALSE;

	if ( !USE_CRYPTO_METHODS )
	{
		return AfxGetApp()->WriteProfileString( lpszSection, lpszEntry, lpszValue );
	}

	if ( !lpszCryptoKey )
		lpszCryptoKey = AfxGetAppName();

	LPBYTE lpcbPassword = (LPBYTE) lpszCryptoKey;
	const DWORD dwPasswordLen = (DWORD)( sizeof( TCHAR ) * _tcslen( lpszCryptoKey ) );

	BYTE lpcbDataValue[PASSWORD_MAXLENGTH];
	const DWORD dwDataValueLen = PASSWORD_MAXLENGTH;
	DWORD dwHowManyBytes = dwDataValueLen;

	BOOL bEncryptionDone = FALSE;
	HCRYPTPROV hCryptoProvider = NULL;
	HCRYPTHASH hCryptoHash = NULL;
	HCRYPTKEY hCryptoKey = NULL;

	ZeroMemory( lpcbDataValue, sizeof( lpcbDataValue ) );
	CopyMemory( lpcbDataValue, lpszValue, dwDataValueLen );

	if ( CryptAcquireContext( &hCryptoProvider, NULL, NULL, PROV_RSA_FULL, 0 ) )
	{
		if ( CryptCreateHash( hCryptoProvider, CALG_MD5, NULL, 0, &hCryptoHash ) )
		{
			if ( CryptHashData( hCryptoHash, lpcbPassword, dwPasswordLen, 0 ) )
			{
				if ( CryptDeriveKey( hCryptoProvider, CALG_RC4, hCryptoHash, CRYPT_EXPORTABLE, &hCryptoKey ) )
				{
					if ( CryptEncrypt( hCryptoKey, NULL, TRUE, 0, lpcbDataValue, &dwHowManyBytes, dwDataValueLen ) )
					{
						bEncryptionDone = AfxGetApp()->WriteProfileBinary( lpszSection, lpszEntry, lpcbDataValue, (UINT)dwHowManyBytes );
					}
					else
					{
						DisplayLastError( _T("CryptEncrypt: ") );
					}
					VERIFY( CryptDestroyKey( hCryptoKey ) );
				}
				else
				{
					DisplayLastError( _T("CryptDeriveKey: ") );
				}
			}
			else
			{
				DisplayLastError( _T("CryptHashData: ") );
			}
			VERIFY( CryptDestroyHash( hCryptoHash ) );
		}
		else
		{
			DisplayLastError( _T("CryptCreateHash: ") );
		}
		VERIFY( CryptReleaseContext( hCryptoProvider, 0 ) );
	}
	else
	{
		DisplayLastError( _T("CryptAcquireContext: ") );
	}

	return bEncryptionDone;
}

Other sensitive data are stored (only into the database) using MySQL's AES_ENCRYPT and AES_DECRYPT functions. You can found more information on this subject here.

And if you would like to run this application against either MS SQL Server or Oracle databases, then you need to adapt the SQL statements from the IssueTrackerExt.cpp file.

Final words

Issue Tracker application uses many components that have been published on The Code Project. Many thanks to:

 

In order to recompile this Visual C++ .NET 2005 project, you should consider downloading the boost::regex library from Boost's Official Page, since I'm using it to read e-mail addresses and issue's dependencies list.

The PJ Naughter's CPJNSMTPConnection class also requires the OpenSSL Project. A good choice would be to download the Windows binaries of the OpenSSL Project from Shining Light Productions page.

Whilst this tool was originally intended for my personal use only, it is now a 'community' project, so if you find it useful and want to make suggestions for enhancements or bug fixes, then post below.

History

  • version 1.00 (July 13th, 2014) - Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mihai MOGA
Architect Printec Group Romania SRL
Romania Romania
My professional background includes knowledge of analyst programmer for Microsoft Visual C++, Microsoft Visual C#, Microsoft Visual Basic, Sun Java, assembly for Intel 80x86 microprocessors, assembly for PIC microcontrollers (produced by Microchip Inc.), relational databases (MySQL, Oracle, SQL Server), concurrent version systems, bug tracking systems, web design (XHTML, CSS, XML, PHP/MySQL, JavaScript, Flash).
 
If you like my articles, please visit my website for more: http://www.mihaimoga.com/
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
Question[CryptoAPI] CryptAcquireContext: Keyset does not exist PinmemberChad Williams15-Oct-14 5:16 
AnswerRe: [CryptoAPI] CryptAcquireContext: Keyset does not exist PinprofessionalMihai MOGA24-Oct-14 11:33 
GeneralMy vote of 5 PinmvpMarcelo Ricardo de Oliveira22-Aug-14 7:09 
GeneralRe: My vote of 5 PinprofessionalMihai MOGA22-Aug-14 7:21 
GeneralGreat idea PinprofessionalMichael N. Haephrati20-Jul-14 6:39 
GeneralRe: Great idea PinprofessionalMihai MOGA20-Jul-14 19:51 
GeneralDatabase script missing PinprofessionalMuhammad Shahid Farooq16-Jul-14 1:42 
GeneralRe: Database script missing PinprofessionalMihai MOGA16-Jul-14 2:13 
GeneralMy vote of 5 Pinmemberdeshjibon15-Jul-14 10:10 
GeneralRe: My vote of 5 PinprofessionalMihai MOGA15-Jul-14 16:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150327.1 | Last Updated 14 Jul 2014
Article Copyright 2014 by Mihai MOGA
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid