Introduction
RunCmd
is a tiny batch file editor/runner utility written in WPF - MVVM pattern. It can be used to create/edit batch files and execute them. RunCmd
also acts as a commonplace for storing our batch files. We can quickly find our batch files (using filter textbox) and run them (double clicking on filename or select and click Run).
The MVVM framework is the minimalistic MVVM framework decribed in this article. I will not go into full details of our MVVM implementation as this can be explored in the article at this link.
Background
Many times, we have to perform some repetitive tasks in Windows environment which can be quickly done using command line. (e.g. Here and here are a list of some Windows system applications we can run using command line.)
Also, as a developer, we often find ourselves in need of finding some specific locations or executing some specific programs - which we know we can automate using command line. For example:
- Automate the launch of our favorite apps and folders - so that our apps & folders are just a double click away every morning.
- Runing our /bin/Debug/myprogram.exe or /bin/Release/myprogram.exe without attaching the Visual Studio debugger to it.
- Or running our myprogram.exe with commandline parameters.)
- Automate launch of Windows Control Panel apps (like "Programs & Features", "IIS Management", "System Properties", "Device Manager", etc.) - this can easily be done by command line/batch files.
- Launching Visual Studio in admin mode {like I use nircmd elevate to do that}
- Running NAnt builds or running other command line utilities.
- These are few uses I've found of this App that are helpful for me. Basically, there can be a number of uses of this simple utility.
Moreover, remembering all the commands might not be savvy either. If we have a utility which can help us create, edit, find and run batch files, this can help us speed up our daily tasks in a considerable way.
RunCmd
tries to do this by giving us a commonplace to create, edit, find and run our batch files.
Using RunCmd
The usage of RunCmd
is quite straightforward.
- Home View
- Home View is the main workspace of our App. On the left, we will see the list of our batch files. On the right, there is Input Textbox (Batch file Contents) at the top and Output Texbox (Output of running the batch file) at the bottom. We have some buttons (Save, New, Run, etc.) which we can use to perform operations.
- We can single click a file in list to select it for editing or running. We can double-click any file in the files list at left to Run it.
- Once selected, the batch file contents are shown in the right hand side top textbox. We can edit the contents and Save (Save only Not Run) or Run it or click "New" to create a new file.
- Once we run the Batch file, we will see the output of the console in "Output" textbox at the right panel.
- Options Window
- The Options Window has some controls to change the settings. The change in settings are notified to the
MainWindow
and saved in config file.
- Logs View
- Logs view is just a display of Logs which
RunCmd
has generated. We may usually want logs if we are directing them to console. Turn off logging from "Options
" window if it's not required. - What we see here is a list of files. We can double click to launch the file. Click "Back" button to go back to Home View.
How It Works
As discussed earlier, the MVVM portion of the application has already been explained in this article. If you would like to know about the MVVM framework we are using, please visit the above link. I will cover the functionality we have added to get RunCmd
here.
1. Home View
We will notice the following in the Document Outline & Design View of HomeView.xaml page (above):
1.a Left Panel (Batch Files List)
In the left panel, we have ScrollViewer
with ListView
which binds to the ListCollectionView
(named BatFilesView
) on the top of batch file names picked from the working directory.
From MSDN:
"You can think of a CollectionView as the layer on top of the binding source collection that allows you to navigate and display the source collection based on sort, filter, and group queries, all without having to manipulate the underlying source collection itself. If the source collection implements the INotifyCollectionChanged interface
, the changes raised by the CollectionChanged
event are propagated to the views."
So our ListCollectionView
(BatFilesView
) adds a filter which is triggerred on the Text
Change of FileNames
filter textbox (which binds to property BatFilesFilterText
in HomeViewModel
. Once this property changes - we trigger the filter - which in turn triggers the List
to update accordingly.)
1.b Right Panel (Batch File Edit textbox and Output Textbox)
On the right in HomeView
, we will notice the Textbox
to edit the Name
& Contents
of our Batch
file. We have some basic buttons which do the standard jobs as per names. ("Save" button saves the current contents to fileName specified. "New" button creates a fresh file and sets the SelectedBatFile
to the New file.) These are pretty much basic CRUD activities what we can also see in our sample MVVM.
What will be of interest to us here will be how the "Run" button's command executes. Below is how the code of Run command looks like. What we will notice here is Command simply creates a process by passing in the batch file that we just now saved as an argument to command prompt. How we are running this process is:
- First of all, we are running this process in a separate thread as we do not want to block the UI thread. We can run it right here also, just that it will simply block the UI thread and we will only get notified once the process is finished. For long running process (or builds), this is kind of annoying. So we run this process in a separate thread. And while this process thread is running - I've attached my handlers to the process to keep the UI thread updated with the current output or errors:
- We attached one handler (
SortOutputHandler
) to grab all the output of process and append it to our buffer (StringBuilder
). We will use this buffer at the end of the process to create our log. - We attached a handler to append the output to the
OutputText
. - We also attached a handler to grab the output of
Error
and append it to OutputText
(if in case our process lands in error.) - So then, we simply run the process in a new thread - and try to make sure that our process terminates properly and we log the outputs of process to our log (
BuildCompleted
handler)
The "Run" Button or our HomeView
is relayed to the following Command
(in HomeViewModel
):
private void ExecRunCmd(object obj)
{
if (ConfirmBeforeRun)
{
MessageBoxResult confirmRunResult = MessageBox.Show
("Are you sure you want to run this file?", "Run Batch File?",
MessageBoxButton.OKCancel);
if (confirmRunResult == MessageBoxResult.Cancel)
{
return;
}
}
SaveOrUpdateCurrentBatFile();
try
{
string[] cmdArgs = new string[]
{
"cmd /C ",
"\""+SelectedBatFile.TextFileName+"\""
};
_process = new Process();
_process.StartInfo = new ProcessStartInfo(ExeFileName)
{
Arguments = string.Join(" ",cmdArgs),
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
CreateNoWindow = true,
WorkingDirectory = Utility.getAppBasePath()
};
_process.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
_process.OutputDataReceived +=
new DataReceivedEventHandler((o, d) => AppendText(d.Data));
_process.ErrorDataReceived +=
new DataReceivedEventHandler((o, d) => AppendError(d.Data));
_process.EnableRaisingEvents = true;
_process.Exited += new EventHandler(BuildCompleted);
TxtOutput = string.Empty;
ThreadStart ths = new ThreadStart(() =>
{
bool ret = _process.Start();
_process.BeginOutputReadLine();
_process.BeginErrorReadLine();
});
_workerThread = new Thread(ths);
_workerThread.IsBackground = true;
_workerThread.Start();
IsCmdRunning = true;
_workerThread.Join();
}
catch (Exception exc)
{
throw (exc);
}
}
2. Options Window (Popup)
Options Window contains the basic controls arranged in a grid. Some lables, checkboxes, buttons to set filenames and Reset button. The functions of these controls are very much simple updating the properties in the OptionsWindowViewModel
which in turn saves the properties to Settings file (simple XmlSerialized
to \config\AppConfig.cfg file).
The point of interest in this window is the messaging mechanism. As soon as one of the properties is updated, this view publishes an ObjMessage
to notify all observers about the change. The Main Window subscribes to the ObjMessage
we publish in the Options Window. We can notice the following Handler
for our ObjMessage
in "HomeViewModel
". (This is how it updates itself as soon as it receives the notification of property change.)
For example, the following code in OptionsWindowViewModel
publishes an ObjMessage
once it is done updating the ConfirmBeforeRun
property:
public bool ConfirmBeforeRun
{
get { return _ConfirmBeforeRun; }
set
{
_ConfirmBeforeRun = value;
Settings.Instance.ConfirmBeforeRun = value;
Settings.Instance.Save();
OnPropertyChanged("ConfirmBeforeRun");
StatusMessage = string.Format
("Will {0} confirm before running batch files!", value ? "" : "not");
ObjMessage message = new ObjMessage("MainView","ConfirmBeforeRun");
EventAggregator.Publish(message);
}
}
Handler
for ObjMessage
in HomeWindowViewModel
:
private void HandleOptionsUpdated(ObjMessage obj)
{
if (obj.Notification.Equals("MainView"))
{
switch (obj.PayLoad.ToString())
{
case ("UpdateSavedCommandsPath"):
SavedCommandsLoc = Settings.Instance.SavedCommandsPath;
break;
case ("UpdateExePath"):
ExeFileName = Settings.Instance.ExePath;
break;
case ("ResetSettings"):
ExeFileName = Settings.Instance.ExePath;
SavedCommandsLoc = Settings.Instance.SavedCommandsPath;
ConfirmBeforeRun = Settings.Instance.ConfirmBeforeRun;
break;
case ("ConfirmBeforeRun"):
ConfirmBeforeRun = Settings.Instance.ConfirmBeforeRun;
break;
default:
break;
}
}
}
3. Logs View
The LogsView
is just a simple list display of log files from \log\* directory relative to RunCmd.exe. Double clicking the file will simply call the System
to launch the file as below:
private void ExecViewLogCmd(object obj)
{
System.Diagnostics.Process.Start(SelectedLogFile.TextFileName);
}
We use our standard navigation system to navigate back & forth between:
private void ExecGoBackCmd(object obj)
{
_eventAggregator.Publish(new NavMessage("HomeView"));
}
Useful Links
History
- 5th November, 2014: Initial publication