Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop

Tiny CMD

4.97/5 (17 votes)
1 Aug 2019CPOL3 min read 26.7K   886  
A tiny Command Line Interface wrapped with a Graphic User Interface

Introduction

Please visit our SourceForge product page.

Image 1

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"
C++
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.

C++
BOOL DoRun(WCHAR *command)
{
    BOOL Result = FALSE;
    DWORD retSize;
    LPTSTR pTemp = NULL;
    TCHAR Command[BUFSIZE] = L"";
    if (!(DeleteFile(RESULTS_FILE)))
    {
        //return L"Can't delete previous results";
    }
    _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.

C++
void SetCommand(CString command)
{
    Command = command;
    HANDLE hThread = (HANDLE)_beginthread(ThreadFunc, 0, NULL);
}

Then the thread function itself goes as follows:

C++
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:

C++
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.

C++
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;
    }
    /*else
    {
        DWORD lastError = GetLastError();
        if (lastError == 32)
        {
            // File is locked since it is being prepared
            return strResult;
        }
        strResult.Format(L"Can't open file %s. Error %d", RESULTS_FILE, lastError);
        return strResult;
    }*/
    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:

Image 2

and then if resized, like this:

Image 3

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:

C++
CFont m_Font;

Then, as part of OnInitDialog, we call:

C++
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:

C++
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():

C++
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.

C++
#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

License

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