Introduction
Please visit our SourceForge product page.
The purpose of this article is to show how to run CMD commands from any application regardless of its type (MFC, Win32, Console), wait for the results and view them using your own user interface.
Background
As part of our daily work at Secured Globe, Inc., we run pre-made programs. All they do is execute a series of CMD commands programmatically while responding to the result. That brought up the idea to build a generic tool for that purpose.
The "How To"
First, we define 3 global variables to store the state of the command's asynchronized processing.
Command
- the command you would have typed in CMD CommandResult
- the result you would have seen on screen when execution is completed IsRunning
- indicates whether the command is still being processed. To test a command that takes longer to process, try typing "netstat
"
CString Command, CommandResult;
BOOL IsRunning = FALSE;
The DoRun() Function
The actual place where we compose the string
sent to ShellExecute() is DoRun()
.
Good to know: you may prefer to use ShellExecuteEx() or CreateProcess().
The DoRun()
function first deletes any old version of result.txt and then properly sends the command.
BOOL DoRun(WCHAR *command)
{
BOOL Result = FALSE;
DWORD retSize;
LPTSTR pTemp = NULL;
TCHAR Command[BUFSIZE] = L"";
if (!(DeleteFile(RESULTS_FILE)))
{
}
_tcscpy_s(Command, L"/C ");
_tcscat_s(Command, command);
_tcscat_s(Command, L" >");
_tcscat_s(Command, RESULTS_FILE);
wprintf(L"Calling:\n%s\n", Command);
Result = (BOOL) ShellExecute(GetActiveWindow(), L"OPEN", L"cmd", Command, NULL, 0L);
if(!Result)
{
retSize = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_ARGUMENT_ARRAY,
NULL,
GetLastError(),
LANG_NEUTRAL,
(LPTSTR)&pTemp,
0,
NULL);
MessageBox(NULL,pTemp,L"Error",MB_OK);
}
return Result;
}
The SetCommand Function
The SetCommand
function is used to initiate a new command via its own thread.
void SetCommand(CString command)
{
Command = command;
HANDLE hThread = (HANDLE)_beginthread(ThreadFunc, 0, NULL);
}
Then the thread function itself goes as follows:
void __cdecl ThreadFunc(void*)
{
CommandResult = L"";
if (DoRun(Command.GetBuffer()))
{
IsRunning = TRUE;
while (IsRunning)
{
if(CheckCommandExecutionStatus())
{
break;
}
}
}
IsRunning = FALSE;
_endthreadex(0);
}
CheckCommandResult()
goes like that:
bool CheckCommandExecutionStatus()
{
CommandResult = GetResultFromFile();
if (CommandResult != L"")
return true;
else
return false;
}
Getting the Results
Upon completion of the command's execution, we expect to have the results in a .txt file created and reused. The file name is "result.txt".
The GetResultFromFile()
function reads this file and returns a CString
with the result.
The commented part can be used for cases where the file can't be opened, however since that isn't expected to happen, I have commented that part.
CString GetResultFromFile()
{
CString strResult = L"";
std::FILE *fp;
fp = NULL;
_wfopen_s(&fp, RESULTS_FILE, L"rb");
if (fp)
{
std::string contents;
std::fseek(fp, 0, SEEK_END);
contents.resize(std::ftell(fp));
std::rewind(fp);
std::fread(&contents[0], 1, contents.size(), fp);
std::fclose(fp);
CString temp1 = (CString)(CStringA)(contents.c_str());
wprintf(L"Result:\n%s\n", temp1.GetBuffer());
if (temp1 == L"") temp1 = L"Unknown command";
strResult = temp1;
}
return strResult;
}
Further Enhancements
Thanks should be given to the author of EASY-SIZE, Marc Richarme. EASY-SIZE can be easily used to support resizing controls within a Dialog box whenever the Dialog is resized.
As a result, the dialog may originally look like this:
and then if resized, like this:
Font Support
I wanted the text to be clear and large enough. I selected Courier, 140 points.
First, we define m_font
as a member variable:
CFont m_Font;
Then, as part of OnInitDialog
, we call:
m_Font.CreatePointFont(140, _T("Courier"));
Then, to assign this font to a specific control (in our case, our Command and Command result, we use the following code:
CommandTyped.SetFont(&m_Font);
Text Color
To set a clear yellow on black text, we use SetTextColor() and SetBkColor(). We add the following calls to OnCtrColor()
:
case CTLCOLOR_EDIT:
if(ID == IDC_CMD_RESULT || ID == IDC_COMMAND)
{
pDC->SetTextColor(COLOR_YELLOW);
pDC->SetBkColor(COLOR_BLACK);
return (HBRUSH)(m_brush->GetSafeHandle());
}
break;
It is always best to define constants separately, and in most cases, reuse them again without having to type their values. In this case, I defined the font name, along with the yellow and black colors in the header file.
#define FONT_NAME _T("Courier")
#define COLOR_BLACK RGB(0, 0, 0)
#define COLOR_YELLOW RGB(204, 204, 0)
Keyboard Shortcuts
To make it easy to operate, and to be consistent with the way CMD
is used, the following keyboard keys are supported:
- <ENTER> - executes a command
- <Up / Down arrow keys> - switch among previous commands given
Bug Fixed and Proofing
Thanks to evlncrn8 for finding a memory leak. and proofing... :)
History
- 1st August, 2019: Initial version