Click here to Skip to main content
15,041,393 members
Articles / Desktop Programming
Article
Posted 1 Aug 2019

Stats

15.3K views
731 downloads
23 bookmarked

Tiny CMD

Rate me:
Please Sign up or sign in to vote.
4.96/5 (15 votes)
1 Aug 2019CPOL3 min read
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)

Share

About the Author

Michael Haephrati
CEO Secured Globe, Inc.
United States United States
Michael Haephrati, Musician and CEO and co-founder of Secured Globe, Inc. Worked on many ventures starting from HarmonySoft, designing Rashumon, the first Graphical Multi-lingual word processor for Amiga computer. During 1995-1996 he worked as a Contractor with Apple at Cupertino.

You can also Buy me coffee.





Comments and Discussions

 
QuestionMy vote of 5 Pin
John Klinner2-Aug-19 21:59
MemberJohn Klinner2-Aug-19 21:59 
QuestionThis is nothing a good shell script can't do Pin
Member 135063092-Aug-19 7:17
MemberMember 135063092-Aug-19 7:17 
AnswerRe: This is nothing a good shell script can't do Pin
Michael Haephrati2-Aug-19 10:03
professionalMichael Haephrati2-Aug-19 10:03 
Questioncpu usage Pin
evlncrn82-Aug-19 5:35
Memberevlncrn82-Aug-19 5:35 
AnswerRe: cpu usage Pin
Michael Haephrati2-Aug-19 10:00
professionalMichael Haephrati2-Aug-19 10:00 
AnswerRe: cpu usage Pin
Michael Haephrati2-Aug-19 10:10
professionalMichael Haephrati2-Aug-19 10:10 
Fixed.
- Michael Haephrati מיכאל האפרתי

QuestionIsRunning or IsRuuning ? Pin
t-j@work2-Aug-19 2:02
professionalt-j@work2-Aug-19 2:02 
AnswerRe: IsRunning or IsRuuning ? Pin
Michael Haephrati2-Aug-19 10:02
professionalMichael Haephrati2-Aug-19 10:02 
AnswerRe: IsRunning or IsRuuning ? Pin
Michael Haephrati2-Aug-19 10:09
professionalMichael Haephrati2-Aug-19 10:09 
Questionmemory leak Pin
evlncrn81-Aug-19 21:30
Memberevlncrn81-Aug-19 21:30 
AnswerRe: memory leak Pin
Michael Haephrati2-Aug-19 10:01
professionalMichael Haephrati2-Aug-19 10:01 
Questionproofread, proofread and then post.. Pin
evlncrn81-Aug-19 21:47
Memberevlncrn81-Aug-19 21:47 
AnswerRe: proofread, proofread and then post.. Pin
Michael Haephrati2-Aug-19 10:22
professionalMichael Haephrati2-Aug-19 10:22 

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

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