August 7, 2007

Keep it Simple – Shortcuts and Applications

Filed under: Automation,Scripting — Marcus Tettmar @ 7:47 am

We had a customer email us the other day having a problem getting his macro to work reliably. He wanted to start an application, so he had written a macro to left click on the application’s shortcut on the desktop. But sometimes the icon moved. He wanted to know how to reliably know where the icon was.

Reality check. There’s an easier way! You don’t need to know where the icon is.

Let’s just think about this for a second. What is a shortcut? As its name suggests it’s a shortcut to something. In this case the shortcut was to an application. We need to start the application. So why not start it directly? Why click on the shortcut at all?

Right clicking on the shortcut and selecting properties tells us the path of the application in the “Target” field. Usually all we need to do is copy that and stick it in a Run Program command:

Run Program>C:\Program Files\FileZilla\FileZilla.exe

Occasionally an application needs to start in a different folder. In the shortcut properties you’ll see a field called “Start in”. If necessary copy the value there and put it in a Change Directory command. So we end up with:

Change Directory>C:\Program Files\FileZilla
Run Program>C:\Program Files\FileZilla\FileZilla.exe

You can even run a shortcut itself:

ExecuteFile>C:\Documents And Settings\Marcus\Desktop\FileZilla.lnk

This way the shortcut is executed just as if you clicked it and all the attributes of the shortcut are used.

No mouse clicks needed!

If you really *do* want to find an icon on the desktop reliably, use Image Recognition.

Or you can even use keystrokes to invoke a shortcut.

SetFocus>Program Manager
Send>FileZilla
Press Enter

But for just starting an application, all this is rather unnecessary. Run the application directly.

July 2, 2007

Methods for Accessing Excel Data

Filed under: Automation,Scripting — Marcus Tettmar @ 11:11 am

UPDATE: 25/09/2012 – This article was written in 2007 before Macro Scheduler contained native Excel functions and native database access. Furthermore, since this article was written Excel no longer supports DDE. This article has therefore been superseded and should only be used as a reference for use with older versions of Macro Scheduler and/or older versions of Excel. For the latest version (currently v13) look at the native Excel functions (XLOpen, XLGetCell, XLSetCell etc) and the native database functions. For an example of querying Excel using the native database functions see this post.

There are a number of ways that Macro Scheduler can interact with Excel to retrieve or modify data. I’ll introduce you to three methods here.

1) Forget Excel!

Yes, sometimes you can forget about Excel. Often people actually just want to read from/write to a CSV (Comma Separated Values) file. Because Excel is on so many people’s computers and is the default file association for CSV files it seems many people think CSV files are Excel files. Not so. If the file you want to read is a .CSV file then it’s really just a text file containing the data. Each line represents a row of data and each column is separated by commas (or sometimes semicolons – but it could be anything else). Open the CSV file up into Notepad and you will see something like this:

"id","description","quantity","price"
5343,"earl gray tea",32,5.64
2435,"decaffeinated coffee",26,4.67
1433,"chocolate cookies",12,3.98

String values may or may not be delimited by quote characters, as in my example above.

We can forget about Excel if we want to read this data in with Macro Scheduler. We can instead just go direct and use the ReadFile or ReadLn functions which are used to access text files. This post shows you how to read a text file in line by line. The only part missing is to extract each field or column from each line (record). Well, as with the ReadFile loop in example 2 of this post, we can use the Separate command, and specify the comma character as the delimiter:

Let>comma=,
Separate>line,comma,fields

This would give us fields_1, fields_2, fields_3 and fields_4 containing each item in the line. Combining that code into my ReadFile loop example to show each field would give:

ReadFile>c:\\\temp\\\test.txt,TheFileData
Separate>TheFileData,CRLF,Lines
Let>comma=,
Let>k=1
Repeat>k
  Let>CurrentLine=Lines_%k%
  //extract each field
  Separate>CurrentLine,comma,fields
  MessageModal>%fields_1% %fields_2% %fields_3%
                       %fields_4%
  Let>k=k+1
Until>k>Lines_Count

Want to remove the quotes from the string fields? Use StringReplace:

StringReplace>fields_2,",,fields_2

To write data out to a CSV file simply use the WriteLn command:

Let>LineToWrite=1234,cornflakes,1,1.99
WriteLn>filename.csv,result,LineToWrite

Update: Here’s another way to read CSV files which also solves the problem of quoted field values containing commas.

2) Use DDE

EDIT: DDE is no longer supported in the latest versions of Excel. See editor’s note at top.

DDE is an abbreviation for Dynamic Data Exchange and is an old protocol for interfacing with applications. It has largely been superseded by other methods such as COM but Microsoft Excel still supports it and it provides a simple way to access cells in Excel directly.

Use the DDERequest command to retrieve an item of data from Excel. Excel must be running and the spreadsheet must already be open. We could easily make our macro open the worksheet for us using the ExecuteFile command (or Run Program). We then use DDERequest something like this:

DDERequest>Excel,c:\\\documents\\\example.xls,R1C1,
      myVariable,60

The first parameter is the Server name, which is always just Excel for Microsoft Excel. The second parameter, the DDE “Topic”, should be set to the filename of the spreadsheet (which must be open). The third parameter specifies the cell to retrieve. This is constructed RnCn (Row n, Column n). Use numeric values for both Row and Column. So Cell A1 would be R1C1. Cell B1 would be R1C2. Cell D14 would be R14C4, etc. The next parameter is the variable name to store the result in. And finally we specify a timeout value in seconds. If the command fails to locate within that amount of time it will timeout and the result variable would contain “DDE_TIMEOUT”.

The second parameter can also contain a sheet name when you want to access data on a particular worksheet. The Sheet name is added after the filename, separated from the filename by a colon. So parameter two would become: Filename:Sheetname

One thing to note is that Excel often appears to append a CRLF sequence to the end of the data. Therefore we should remove it as follows:

StringReplace>myVariable,CRLF,,myVariable

Take a look at the “Extract From Excel” sample script which comes with Macro Scheduler for an example which uses the DDE method to retrieve data from Excel and paste it into Notepad.

To modify a cell value, or write a value to Excel we use the DDEPoke command as follows:

DDEPoke>Excel,c:\\\documents\\\example.xls,R2C3,
                                      56.75

Again, the spreadsheet must be open for this to work. The above puts the value 56.75 into cell C2 (row 2, column 3). As with DDERequest you can add a worksheet name in parameter two if needed (Filename:SheetName).

For another example see:
Exchange Data with Microsoft Excel The Easy Way

3) VBScript via COM

VBScript offers the most flexible and powerful method of interacting with Microsoft products. Not only can we modify data using this method we can also control almost any part of Excel. Anything that you can program or record in Excel can be converted into VBScript code and used inside a Macro Scheduler script.

Here is an example script with some functions you can use to retrieve and modify Excel cells:

//Put this VBSTART..VBEND block at top of script
//to declare the functions once
VBSTART
Dim xlApp
Dim xlBook

'Opens the Excel file in Excel
Sub OpenExcelFile(filename)
  Set xlApp = CreateObject("Excel.Application")
  xlApp.visible = true
  Set xlBook = xlApp.Workbooks.open(filename)
end sub

'Use this to close Excel later
Sub CloseExcel
   xlApp.quit
   Set xlApp = Nothing
End Sub

'Retrieves a cell value from the specified
'worksheet
Function GetCell(Sheet,Row,Column)
  Dim xlSheet
  Set xlSheet = xlBook.Worksheets(Sheet)
  GetCell = xlSheet.Cells(Row, Column).Value
End Function

'Sets specified cell of specified worksheet
Function SetCell(Sheet,Row,Column,NewValue)
  Dim xlSheet
  Set xlSheet = xlBook.Worksheets(Sheet)
  xlSheet.Cells(Row,Column).Value = NewValue
End Function
VBEND

//Do the business
VBRun>OpenExcelFile,%SCRIPT_DIR%\example.xls
VBEval>GetCell("Sheet1",5,4),theValue
MessageModal>Cell value: %thevalue%
VBEval>SetCell("Sheet1",28,2,998),nul
//VBRun>CloseExcel

This code is constructed in such a way to allow you to call the VBScript functions from regular MacroScript code and therefore retrieve the data to regular variables.

For another example see:
Automate Excel with VBScript

You can also run Excel macros:
Run Excel Macros and VBA Routines

And, as stated above, you can do a whole heap of other things. Search the forums for Excel to find other examples. Converting from Excel VBA to Macro Scheduler VBScript is a topic for another day but hopefully you can get an idea from these simple examples.

___
You may also be interested in: Accessing Databases

Reading through text files in memory

Filed under: Scripting — Marcus Tettmar @ 10:20 am

Looping through a text file is commonly achieved with the ReadLn command in a loop, like the example in the help file:

Let>k=1
Label>start
ReadLn>c:\\\temp\\\test.txt,k,line
If>line=##EOF##,finish
MessageModal>line
Let>k=k+1
Goto>start
Label>finish

This uses a very simple loop and loops through the file one line at a time using the ReadLn command and a counter variable.

An alternative method is to read the entire file into memory and then put the lines into an array. This means seeking through the file becomes faster, and it becomes easier to change individual lines. Use the ReadFile command to read the entire file content into a variable:

ReadFile>c:\\\temp\\\test.txt,TheFileData

We now have a variable called TheFileData containing all the lines in the file. Each line will be separated by a Carriage Return, Line Feed combination (CRLF). So we can now split the file into an array using the Separate command, specifying CRLF as the delimiter:

Separate>TheFileData,CRLF,Lines

Lines_Count now tells us how many lines there are and we can access each line with Lines_1, Lines_2, etc. So here’s the new version of the full script:

ReadFile>c:\\\temp\\\test.txt,TheFileData
Separate>TheFileData,CRLF,Lines
Let>k=1
Repeat>k
  Let>CurrentLine=Lines_%k%
  MessageModal>CurrentLine
  Let>k=k+1
Until>k>Lines_Count

June 27, 2007

Office 2007 VBA Shell Bug?

Filed under: Automation,Scripting — Marcus Tettmar @ 3:12 pm

When trying to execute a third party application, such as Macro Scheduler, from VBA in Office 2007 using the Shell function the following error is returned:

“Invalid procedure call or argument”

Sometimes you might want to run a Macro Scheduler macro from Office using this method and get this error. Searching the web reveals many people reporting the same error when trying to run other applications, including other Office apps! E.g.:

Shell “c:\my macros\macroA.exe”

I suspect there’s an issue with spaces in the path but trying to embed extra quotes with chr(34) doesn’t seem to help. The Shell function also doesn’t provide a way to specify command line parameters other than including them in the entire command string.

The solution is to use the ShellExecute API function. Declare the function in the General Declarations section of your VBA module using this code:

Private Declare Function ShellExecute Lib “shell32.dll” Alias “ShellExecuteA” (ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long

Then you can use the function as follows:

ShellExecute 0&, “”, “c:\program files\macro scheduler\msched.exe”, “calculator”, “”, 1

This code runs the Calculator sample macro in Macro Scheduler.

To run an application with no command line parameters leave the fourth parameter empty:

ShellExecute 0&, “”, “c:\my macros\mymacro.exe”, “”, “”, 1

May 18, 2007

First Winner of Scripts & Tips Competition

Filed under: Announcements,Automation,Scripting — Marcus Tettmar @ 1:19 pm

We have our first successful entry to the Scripts & Tips 10 Year Anniversary Competition. Robert White sent in this excellent Cookie Monitor script. It’s a nicely written script which is both very useful and also demonstrates effective use of a non-modal dialog and other scripting features such as looping, reading a list of files, string manipulation, and reading from the registry. The beauty of this script, as a demonstration script, is you can run it as-is and it will work on your machine without any modification. Grab the script here.

We’ve rewarded Robert with an extra year of upgrade protection. Well done Robert, and thanks for the script!

February 20, 2007

How to Use Image Recognition

Filed under: Automation,Scripting — Marcus Tettmar @ 12:11 pm

What is Image Recognition?

Update. Image Recognition has been vastly improved and simplified since this article was written and in v13 we introduced an Image Recognition Code Wizard. Watch this video to see how simple it is.

Image Recognition allows Macro Scheduler to find a bitmap (needle) in another, larger, bitmap (haystack) and return its position. The larger bitmap could be a snapshot of the screen, and the smaller bitmap could be a capture of a toolbar button or some other screen object. Therefore Image Recognition allows us to locate objects on the screen, graphically. We can also wait for images to appear on the screen. So Image Recognition allows us to automate any kind of graphical interface. We can make the script wait for images to appear on the screen, find screen objects and therefore send mouse events to the correct part of the screen to focus or control the objects.

The functions most used for Image Recognition are as follows:

WaitScreenImage
FindImagePos
GetScreenRes*
ScreenCapture*

*Note: GetScreenRes and ScreenCapture are no longer required (since v9.2) as you can now specify SCREEN for the haystack in FindImagePos

Building an Image Recognition Script

In this video I demonstrate the Image Recognition functions to automate Outlook running in a remote Citrix session. Traditionally automating interfaces in remote environments is difficult without having Macro Scheduler running on the remote server. Trying to automate a remote desktop with traditional commands is unreliable since the client doesn’t know anything about the windows and objects running on the remote server – all it sees is a graphical copy of the screen. This is why image recognition is so powerful in such situations.

Watch the video and then continue reading this article to learn how the script works.

In the video I first tell Macro Scheduler to find the position of a button on the screen. I first want the macro to find this button:

New Email Button

First I use the image capture tool to capture an image of this button from the screen and save it to a .bmp file. The image capture tool is available in the editor under the Tools menu, and also from the Capture button on the Command Builder dialog for the FindImagePos and other image recognition functions. I save the image to d:\citrix-images\new2.bmp.

The first thing I need my script to do is determine the dimensions of the screen. This is so that I can capture the entire screen later in the script. So I use the GetScreenRes function:

GetScreenRes

This returns the width and height of the screen in variables sX and sY. Next we capture the current screen to d:\screen.bmp using the ScreenCapture command:

ScreenCapture

So this captures the entire screen, from 0,0 to sX,sY returned by GetScreenRes.

Note: Since version 9.2 the above two steps are no longer necessary as it is now possible to specify SCREEN in FindImagePos and do not have to first copy the screen to a bitmap

Now we want the script to look for the new email button in this screen image using the FindImagePos function. This looks like this:

FindImagePos

This looks for new2.bmp inside screen.bmp using a color tolerance of 20 (where 0 is not tolerant and the pixel colors must match exactly and 255 is highly tolerant and anything would match!). We set the fourth parameter to 1 to tell it to return the center coordinates of the image match. If this was zero it would return the top left position in screen.bmp where a match was found. But I would like to return the coordinates of the center position of a match. i.e. the center of the new email button on the screen. XPos and YPos are our variables the coordinates will be stored in. These are arrays. So the first match will be in XPos_0,YPos_0, the second match (if any) in XPos_1, YPos_1, etc. imgs is the return variable which is set to the number of matches found.

So now we can say “if a match was found, move the mouse to the first position and click”. This looks like:

MouseMove

Since we captured the entire screen, the coordinates returned by FindImagePos map directly to screen coordinates. If the ScreenCapture command had not captured the entire screen we would have to add the top x,y coordinates given in the ScreenCapture command as offsets to the MouseMove command. As we captured the entire screen x,y was 0,0 anyway. Sometimes it is not necessary to work with the entire screen and we could instead capture just a Window. Like this:

MouseMove Relative

One would then have to add X,Y to any position returned by FindImagePos in order to map to an absolute screen position.

But back to the demo. We now have code which finds the new email button on the screen and clicks on it. This causes a new window to appear. If we were automating an application running on our desktop we could simply use the good old WaitWindowOpen command which is given the window title and waits for that window to appear. But, remember, this demo is automating a remote environment. The windows are on another computer. We don’t know about window titles. Citrix just sends us an image of the screen of the remote computer. So instead we will use the WaitScreenImage function. This is nice and easy:

WaitScreenImage

I used the capture tool to capture an image of the top left part of the new window – showing the window icon and title:

Window Title

That was saved to title2.bmp. So the above function simply watches the screen and waits until it finds this image on the screen. When that happens we know the new window has appeared.

By the way, you could create your own WaitScreenImage using a loop containing ScreenCapture and FindImagePos. This would give you more control and build in a timeout if required. But WaitScreenImage is a quick and easy way to wait for an image to appear on the screen without any further coding.

Finally, now the script knows the new window is present it can send some keystrokes, so we send some text to create the email. In the real world we’d probably use more image recognition to manipulate other objects on this new window and continue using the same techniques.

send keystrokes

Tips

For performance reasons the image recognition functions do not check every single pixel in each image. That would take far too long. Instead a random sample of pixels is taken from the small “needle” image and each of these is checked against all the possible matching pixels in the larger “haystack” image. For this reason it is possible to get false positives if the image you have captured is not very specific. E.g. consider these two images:

done

connecting

These images are largely similar since we have too much gray area. If the random sample is all from the gray area both would match. These are from a status bar, and if we were searching for Done we could easily find Connecting. The solution here is to make the image smaller so that there is less of the gray background. Alternatively find something more specific on the screen.

Note: You can override this behaviour by setting the number of pixels to scan using the FIP_SCANPIXELS variable, and since v12 this can be set to ALL to perform an exhaustive match and tests reveal no noticeable performance hit in doing so.

AppNavigator Does it Without Code

AppNavigator takes all the coding away from image recognition and also adds some more power. It is clever enough to narrow down a search if it first finds too many matches, or none at all, and it also remembers the part of the screen a match was found in. Next time it will concentrate on that part of the screen and only search the entire screen if a match is not found. This increases performance and reliability. With AppNavigator you can capture screen objects and assign them actions without having to write code like that presented here. AppNavigator can be told what to do when an object can’t be found, and can also navigate the extraction of data from Excel or Access without coding that either.

To see how easy it is to create the same process with AppNavigator see my next post.

Update 12th October 2007: Macro Scheduler 9.2 (and above) now lets you specify SCREEN in the haystack parameter of FindImagePos. Therefore if you are scanning the entire screen you no longer need to use GetScreenRes and ScreenCapture to capture the screen to a bitmap file first. Just specify SCREEN in FindImagePos.

December 5, 2006

Web Automation in Vista

Filed under: Announcements,Automation,Scripting,Vista — Marcus Tettmar @ 1:51 pm

To run WebRecorder, or VBScript macros which automate Internet Explorer in Vista (IE7), you will first need to disable “Protected Mode”. Consider this simple VBScript code:

Dim IE
Set IE = CreateObject(“InternetExplorer.Application”)
IE.Visible=1
IE.Navigate “http://www.mjtnet.com”

In IE6 and in IE7 under XP this code would create a new IE instance and then navigate it to mjtnet.com. But Vista adds something to IE called “Protected Mode” and for some reason Protected Mode causes the Navigate method to open up a new IE window. This is most annoying because it means any subsequent code in a script like the one above would fail because IE object refers to the instance created with the CreateObject call, but the page is now in the second instance.

You can disable “Protected Mode” in IE under Tools/Internet Options/Security. This puts things back to normal and means code like the above will continue to work.

One known issue with WebRecorder’s IEAuto.DLL is that the WaitNewIE function hangs when using IE7. We’re working on this now. This function is only used when an action in IE causes a new window/tab to appear. Apart from that, once you have disabled “Protected Mode” WebRecorder/IEAuto works fine with IE7/Vista.

Update: Version 1.78 is now available, fixing the issue with WaitNewIE on IE7.

November 16, 2006

Sorting Bubbles

Filed under: Scripting — Marcus Tettmar @ 9:12 am

I’ve just posted a tip to Scripts ‘n Tips showing a simple Bubble Sort subroutine. The example sorts a list of files returned by the GetFileList function. I hope it’s useful. Here it is.

September 7, 2006

Get Selected Tab Index

Filed under: Automation,Scripting — Marcus Tettmar @ 6:51 am

As you probably already know, selecting a page of a tab control is usually best achieved by sending CTRL-TAB. So on opening an application we can use the following Macro Scheduler code to jump to the third tab:

SetFocus>Window_Title
Press CTRL
Press Tab * 2
Release CTRL

This issues CTRL-TAB twice and therefore cycles through two tab selections, leaving the third tab selected.

Usually when the window is first opened, the first tab is selected. But what if that is not always the case? How can we determine which tab is currently selected? This question came up in the forum recently.

To do this we can use the Windows API and send the TCM_GETCURSEL message to the tab control. When this message is sent to the control it returns the index of the selected tab.

Here’s an example. Right click on the desktop to open the Desktop Properties dialog and select one of the tabs to change the page. Then run this script:

//Initialise constants
Let>TCM_GETCURSEL=4875

//Get the handle of the Display Properties Window
GetWindowHandle>Display Properties,hwnd

//Find the handle of the SysTabControl32 (Tab Control) object
LibFunc>User32,FindWindowExA,tabctrl,hwnd,0,SysTabControl32,

//Send the TCM_GETCURSEL message to retrieve the selected tab index
LibFunc>user32,SendMessageA,tabindex,tabctrl,TCM_GETCURSEL,0,0

//Display the result
MessageModal>Selected Tab: %tabindex%

This finds the handle of the SysTabControl32 object on the Display Properties dialog and sends it the TCM_GETCURSEL message to retrieve the selected tab index.

You can use this approach with other applications. However, some applications use a different class of tab control, so you’d need to determine the class name of the tab control using the View System Windows tool in Macro Scheduler.

July 13, 2006

Activating System Tray Icons

Filed under: Automation,Scripting — Marcus Tettmar @ 8:47 am

This one has bugged me for years – how to automate activating a system tray icon. For starters, the System Tray is designed to be manipulated with the mouse. We could send mouse clicks to icons in the system tray but for the fact that icons don’t always appear in the same place in the tray. Some systems contract and expand the system tray and on startup icons can be reshuffled and appear in a different order. So sending mouse events is no good.

Well it turns out there is a way to control the system tray with the keyboard. I hadn’t realised this. Thanks to Henk for discovering this. Henk demonstrates here that if you press the Windows key to open the start menu, then Escape to close the start menu keyboard focus is now on the Start button. If you then press Tab a few times focus ends up in the System Tray. If you hover over a System Tray icon you will see a Tool Tip appear above it showing the name of the application. Well the first letter of this text will select this icon. So once you’ve tabbed into the System Tray you can now press the letter key corresponding to the first letter of the icon’s title and it will be selected. Then pressing Enter will activate it.

The number of tabs you need to send to get to the System Tray depends on how you have your task bar set up. If you have the QuickLaunch toolbar enabled you’d need an extra Tab. And in XP with the contracting/expanding System Tray you need to press the right arrow key to move off the contract/expand button. So this approach is not entirely portable but will work on your system.

So I gave this some more thought and came up with a portable approach which focuses the System Tray area directly – without having to open the Start menu and tab around. My approach uses the Win32 API functions FindWindow and FindWindowEx to find the handle of the System Tray Notification Area and then focus it directly. Here’s the code:

Let>WIN_USEHANDLE=1
LibFunc>User32,FindWindowA,h1,Shell_TrayWnd,
LibFunc>User32,FindWindowExA,h2,h1,0,TrayNotifyWnd,
LibFunc>User32,FindWindowExA,h3,h2,0,SysPager,
LibFunc>User32,FindWindowExA,h4,h3,0,ToolbarWindow32,Notification Area
SetFocus>h4
//Change to first letter of icon to activate
Send>v
//If more than one with same first letter, then repeat above line for each
Press Enter

Thanks again to Henk for the tip. See the forum post here.

« Newer PostsOlder Posts »