Introduction
The automator tool may be used to automate a set of repetitive tasks based on key presses. The tasks themselves are configurable (in ksq files). The tool can also reference XML files for data to use with the key sequences.
History
I wrote this tool to enter bugs into a bug tracking tool. People would send me bug files to enter them into a tool that they could not access and I would use this to automatically enter the xml bug files into the tool. So if you see some code/variables specific to a bug entry tool, you know where it came from. :-)
Samples
Along with the demo are samples that u can try out. Open the .ksq file in some text editor and it has instructions at the top on how to run the sample.
Using the demo application
- Choose a key sequence file which will be the set of key sequences that the tool will run.
- Choose the title of a process that the application will execute the key sequnces on.
- Check "No data files required" if your key sequence is not referencing any XML file.
- If your ksq file is referncing an XML file then add it to the data files list.
- Make sure the target application is running (keep it side by side if you want to see what is happening).
- Choose Run.
For writing your own key sequences check the site: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemwindowsformssendkeysclasstopic.asp
Additionally the following custom key sequences can be used
a) WAIT:n
Where n
is the number of seconds that the application must pause before sending next keys.
Eg. WAIT:3
b) REPEAT:
and REPEATEND:
To repeat the key sequences in between the specified number of times.
Eg. REPEAT:3
{ENTER}
REPEATEND:
c)XML:
To access the specified element value in the current xml file.
Eg. XML:ELEMENT1
d)
Specifies a comment. Everything after a
anywhere is a comment.
See the sample files sample1.ksq and sample2.ksq to clarify the above commands.
Code
I'll attempt to explain some relevant parts of the code here.
1. Matching the process title:
this.processWindowHandle = IntPtr.Zero;
Process[] plist = Process.GetProcesses();
foreach (Process p in plist)
{
if (!this.processWindowHandle.Equals(IntPtr.Zero))
{
break;
}
switch(this.titlePositionComboBox.SelectedItem.ToString().ToUpper())
{
case "BEGIN":
if (p.MainWindowTitle.StartsWith(this.targetAppTitleTextBox.Text))
{
this.processWindowHandle = p.MainWindowHandle;
}
break;
.
.
}
}
The program loops through the list of running processes and tries to match the title of the process given.
2. Platform Invoke to activate target application
[DllImport("User32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd
);
[DllImport("User32.dll")]
private static extern bool SetActiveWindow(IntPtr hWnd
);
SetForegroundWindow(this.processWindowHandle);
SetActiveWindow(this.processWindowHandle);
There is no direct call in the .NET api to activate a window. Hence I reference the relevant win32 calls using the DllImport
attribute and call the function with the process handle of the target application I found in step 1.
3. Executing key sequence with xml reference
XPathNavigator nav;
try
{
XPathDocument xpDoc = new XPathDocument(xmlFileName);
nav = xpDoc.CreateNavigator();
}
catch(Exception ex)
{
bufFileItem.SubItems[1].Text = "Error! in xml document.";
continue;
}
ExecuteKeySequence(nav);
We create an XPathDocument
of the xml file. This will allow us to use XPath search/select commands to find data easily.
4. Repeat stack
struct RepeatInfo
{
public int repeatStartPos;
public int repeatCount;
public RepeatInfo(int repeatStartPos, int repeatCount)
{
this.repeatCount = repeatCount;
this.repeatStartPos = repeatStartPos;
}
}
While implementing the key sequence I had to extend the sequences to include control structures. One of the essentials was looping. The structure above represents a repeat loop. Whenever the code finds a REPEAT:
in the key sequence, it adds a repeat info entry into a stack and repeats that part of the key sequence until REPEATEND:
for the specified number of times. The above structure, RepeatInfo
, stores the repeat information: start position of repeat and the number of times left to repeat respectively.
if (s.StartsWith("REPEAT:"))
{
int repeatCount = Convert.ToInt32(s.Replace("REPEAT:", "").Trim());
repeatStack.Push(new RepeatInfo(cmdIndex, repeatCount));
continue;
}
else if (s.StartsWith("REPEATEND:"))
{
if (repeatStack.Count >= 1)
{
RepeatInfo lastRepeatInfo = (RepeatInfo)repeatStack.Pop();
if (lastRepeatInfo.repeatCount > 1)
{
cmdIndex = lastRepeatInfo.repeatStartPos;
lastRepeatInfo.repeatCount--;
repeatStack.Push(lastRepeatInfo);
}
}
continue;
}
5. ExecuteKeySequence
This function loops through the set of key sequences. It checks the type of key sequence - if it is a special key sequence like WAIT
, REPEAT
, REPEATEND
,
or XML
or whether they are keys to be entered on the target application. Depending on the type, the program extracts or assigns keys that are to be sent to the application.
while (cmdIndex+1 < this.sendKeysSequence.Length)
{
cmdIndex++;
string s = this.sendKeysSequence[cmdIndex];
string sendChars = "";
if (s.StartsWith("XML:"))
{
if (nav == null)
{
throw new Exception("Error. XML navigator was null. " +
"No file associated with XML: command "+ s);
}
string xmlName = s.Replace("XML:", "");
XPathNodeIterator iterator = nav.Select("//" + xmlName);
if (iterator.MoveNext())
{
sendChars = iterator.Current.Value;
foreach(string specialChar in this.specialCharsInSendKeys)
{
sendChars = sendChars.Replace(specialChar, "{"+specialChar+"}");
}
}
}
else if (s.StartsWith("WAIT:"))
{
int sleepTime = 5000;
sleepTime = Convert.ToInt32(s.Replace("WAIT:", ""))*1000;
Thread.Sleep(sleepTime);
continue;
}
} ...
Finally we send the keys to the application using the SendKeys method. Note that the target application needs to be active at this point.
SendKeys.SendWait(sendChars);
Further ahead
There are quite a few issues in the program. This program assumes ideal case - if there are unexpected dialogs (like error dialogs) or changes in UI, then this will not work. If another window becomes active while the program is running, the key sequences will be sent to that application; I can't quite decide if that is a feature or a bug.
The code was written to suit my needs and for the most part it was quickly thrown together. I added a few itty bitty features thereafter but they were mostly make up. So the core of the whole program itself could possibly be improved. Adding more control structures like IF-
THEN
, GOTO
etc. would be cool too. Best of luck!