I just posted a script to the Scripts ‘n Tips forum with a function to perform an HTTP file upload.
June 3, 2009
May 19, 2009
Multiple Monitors aid Productivity and Debugging
If you’re not using more than one monitor, you are missing out big time. For one thing, some research by the University of Utah found that using two monitors increases productivity 44%. There’s a good summary and more comment on this on the coding horror blog.
A huge benefit of multiple monitors for Macro Scheduler developers is that it makes developing and debugging automation macros a lot easier. When I’m building a script that controls another application I will often put the Macro Scheduler editor on one monitor and the application I’m automating on another. I can then see both side by side, so I don’t need to switch focus back and forth. I can run my macro as I’m developing it and see the script at the same time as the results. If I need to debug I can step through the script and see the progress of the script at the same time as the outcome without the changing of focus effecting it.
Debugging a script that simulates a user and needs to change focus can be a bit of a conundrum, since the act of debugging introduces delays, allowing more time for events to complete, and causes loss of focus. In Macro Scheduler there’s a “Refocus Windows” setting in the Debugger, but even that isn’t enough in some cases. Being able to work on the macro and see the target application at the same time without either interfering with each other is therefore the best solution.
If you don’t have a PC or video adaptor that can support more than one monitor you could use ZoneScreen along with a laptop or second PC to act as your second screen. A single monitor big enough to let you put the editor and target apps side by side without them overlapping would work too.
If you’re stuck with a small monitor and simply can’t have both editor and target application visible at the same time – you may be at a client’s site or working on a notebook – and need to debug code that needs to see the screen, don’t forget you can also set and run to breakpoints. With a breakpoint you can step through the code and at any time run to the next breakpoint, allowing the macro to whizz through the code to that point without switching focus back to the editor between each step. So for crucial sections of code which need to, say, capture a screen or scrape some text, it can be very useful. Once the script reaches the breakpoint you will be returned to the editor where you can continue stepping line by line, or run to the next breakpoint or end.
In my opinion multiple monitors are an absolute must. But there are limits!
May 12, 2009
The Variable Explorer
An experienced Macro Scheduler scripter was recently trying to figure out why the following code wasn’t doing what he expected:
If>seg_1=05
Let>monLtr=mm
Endif
Apparantly monLtr was always being set to 05. This told me that “mm” must have been a variable which had earlier been set to 05. But my friend said “I’ve looked all through the code I can’t see where “mm” is set anywhere”.
Then I reminded him of the Variable Explorer. “The what?” he asked.
Under the editor select Tools/Variable Explorer or hit Ctrl-Alt-V and you’ll get a box like this one. It shows a list of all the variables created by your script.
Bingo! There’s mm. Expand it and you’ll see all the lines where it is set/created. In this case it’s created on line 40 by the Min command.
In a long script it’s easy not to see the obvious. The Variable Explorer makes it easier.
Of course, it would also be sensible to use a better naming convention for the variable to avoid such confusion. Or use VAREXPLICIT or he could have used {“mm”} to specify the literal string value. But don’t forget the Variable Explorer as it can save a lot of hunting around.
April 30, 2009
Regular Expressions for Dummies
I recently stumbled upon this series of video tutorials on Regular Expressions:
Regular Expressions for Dummies
If you want to know how to get some power of out of Macro Scheduler‘s new RegEx function you might find the above free video tutorial useful.
One resource seen in the video that I didn’t know about is RegExr – an Online Regular Expression Testing Tool. It’s free to use and there’s a desktop version you can download too.
But my favourite RegEx tool is RegExBuddy from JG Soft. An invaluable tool for anyone working with Regular Expressions. The author also maintains this excellent Regular Expression resource which includes a tutorial, examples and references.
April 20, 2009
Twittering from Macro Scheduler with the Twitter API
Way back in the deep and distant past when the Internet was new and Bill Gates thought it was just a passing fad, I remember reading about a Cola vending machine on a University campus that some frivolous young boffins hooked up to the ‘net so that you could check its inventory from anywhere in the world using an old fashioned network command called “finger”. Why? Because they could.
Fast forward to the technologies of the current day and the latest trend of Twitter, and history is repeating itself. In the last week I’ve read about a restaurant that can take orders via Twitter, a bakery tweeting the emergence of fresh loaves from the oven; and, utterly pointless, some guys who created a system which sends a tweet every time their cat enters or exits its cat flap. Why? Well, because they can I guess.
Not wanting to be left out I decided to write some Macro Scheduler code to tweet status updates and monitor replies. Why? Well there might be a good reason for being able to do this – I’m sure someone will have one. Perhaps you have a client who wants you to set up a system to monitor the movement of his cat, process restaurant orders, or your local baker wants an automated fresh-loaf tweeter! But mostly, it’s because we can.
You’ll find the Twitter API documentation here. Here’s the code to Tweet a status update:
Let>username=YOURTWITTERNAME Let>password=YOURPASSWORD //Tweet from Macro Scheduler Let>url=http://%username%:%password%@twitter.com/statuses/update.xml Let>message=Kitty just left the buildng HTTPRequest>url,,POST,status=%message%,result
Being serious for a moment I can see how a macro that monitors an application might want to post status updates to Twitter, or a backup script could alert you by Twitter when there’s a problem. It might be a public system, but don’t forget that Twitter profiles can be made private too, and Tweets can be viewed on and sent from your BlackBerry, iPhone, or even by SMS.
The following script sets up a loop which monitors your Twitter stream for “mentions” of your username. This might form the basis of a script which retrieves orders. Perhaps it could listen to Twitter for commands and carry out actions based on what message was sent. Or perhaps you just want a macro which does something when a cat decides to head out for the night. Use your imagination.
Let>username=YOURTWITTERNAME Let>password=YOURPASSWORD Let>ini_file=%SCRIPT_DIR%\twit.ini Let>_delay=30 VBSTART VBEND //monitor twitter username "mentions" loop Label>monitor_loop Let>url=http://%username%:%password%@twitter.com/statuses/mentions.xml HTTPRequest>url,,GET,,result //remove theportion (I don't need it and it avoids distinguishing the text IDs from the user IDs. RegEx> [^>](.*?) ,result,0,user_matches,nf,1,,result //extract all texts RegEx>(?<=)[^>]*?(?= ),result,0,text_matches,num_texts,0 If>num_texts>0 //extract all ids RegEx>(?<=)[^>]*?(?= ),result,0,id_matches,num_ids,0 //get last known Let>last_known_id=0 IfFileExists>ini_file ReadIniFile>ini_file,SETTINGS,LAST_ID,last_known_id Else WriteLn>ini_file,wlnr, Endif //iterate through texts Let>k=0 Repeat>k Let>k=k+1 Let>this_id=id_matches_%k% If>this_id>last_known_id Let>msg_text=text_matches_%k% /* msg_text contains the message Use your imagination here! For now we'll show it in a message */ MessageModal>msg_text Endif Until>k=num_texts //store last ID EditIniFile>ini_file,SETTINGS,LAST_ID,id_matches_1 Endif Wait>_delay Goto>monitor_loop
The script retrieves the 20 most recent “mentions”. It stores the last seen ID in an INI file so that on the next check it ignores those it has seen before, only retrieving messages with a larger ID number.
This is a quick and dirty solution with no error checking, using RegEx to parse the XML that is returned by the call to Twitter. You may prefer to use the MS XML object as shown here.
Whether this proves useful or completely pointless, I hope you have fun. If you’re using Macro Scheduler with Twitter, please add a comment below to let us know how … and why!
Don’t forget you can follow me on Twitter where I may occassionally say something useful.
April 17, 2009
Working with Windows API Functions
Macro Scheduler‘s LibFunc command allows you to run functions contained inside DLLs. A DLL, or Dynamic Link Library, is a file which contains functions that other programs can use. Windows includes a number of DLLs containing lots of functions that make Windows tick, and other applications are able to call them. These functions are known as the Windows API (Application Programming Interface). Using LibFunc a Macro Scheduler script can access some of these functions too.
The Windows API Reference can be found here:
http://msdn.microsoft.com/en-us/library/aa383749(VS.85).aspx
This provides a list of functions which you can browse alphabetically or by category.
Data Types
Before I go on I should mention data types. Not every Windows API function can be used by Macro Scheduler. This is because Macro Scheduler does not know about every possible Windows data type. Macro Scheduler currently only knows about integers, long integers and strings. Almost any function that requires or returns integer and/or character based data can be called. But anything that requires, for example, a record structure or a callback function can not be used.
The API documentation lists the Windows data types here:
http://msdn.microsoft.com/en-us/library/aa383751(VS.85).aspx
Many of these are based on the same fundamental data types. E.g. all the HANDLE types are just unsigned numbers, so are supported by Macro Scheduler’s long integer. LPTSTR and LPCTSTR are interchangeable with strings. From this list only CALLBACK and FLOAT are NOT compatible.
So, as long as the function requires or returns only bools, integers or strings, we should be able to use the function in Macro Scheduler. Note that BOOLs are really just integers. A BOOL is an integer with value 1 (true) or 0 (false).
An Example: CopyFile
Let’s look at a Windows API function and how we can use it in Macro Scheduler.
Take a look at the CopyFile function:
http://msdn.microsoft.com/en-us/library/aa363851(VS.85).aspx
At the top of the page we are told what this function does:
“Copies an existing file to a new file.”
We are then given the syntax. You’ll notice that it is provided using C++ syntax. It certainly helps if you know C++ but it is not essential and hopefully this example will help you understand what the syntax definition is telling us:
BOOL WINAPI CopyFile( __in LPCTSTR lpExistingFileName, __in LPCTSTR lpNewFileName, __in BOOL bFailIfExists );
The first thing this tells us is that CopyFile returns a BOOL (0 or 1). Inside the parenthesis we see that the function requires three parameters. The first two are of type LPCTSTR. For our purposes this means it is a string. The third parameter is a BOOL again.
We are then told what each parameter is for, what the function returns and various remarks.
While the names of the parameters are quite self explanatory the documentation gives us more detail. So we can see that the first parameter lpExistingFileName represents the name of an existing file, lpNewFileName is what we set to the name of the new file we want to copy to, and bFailIfExists can be set to true (1) to make the function fail if the new file already exists or false (0) to overwrite.
We are told that the function returns zero if the function fails, or non zero if it succeeds.
DLL and Function Name
At the end of the page is some information crucial to us in the Requirements section. This tells us which versions of the operating system support this function and what DLL it is contained in – Kernel32.dll in this case. Note also that it tells us the alias names of the function. In this case CopyFileA for the ANSI version and CopyFileW for the unicode version (Why “W” not “U” I hear you ask – W stands for WideString, a special form of string which can contain double byte characters).
So, putting it all together, we end up with the following LibFunc call:
LibFunc>kernel32.dll,CopyFileA,result,c:\source.txt,c:\my docs\new.txt,0
From left to right LibFunc takes the DLL name, then the function name, a variable which should return the result of the call and then the values to pass to the function. One thing to be aware of is that DLL function names are case sensitive. Make sure the function name is entered into the LibFunc command exactly as specified in the API documentation.
Try the above line with a real filename to see it in action.
Passing by Reference
Some DLL functions modify the values being passed to them. Parameters are passed by reference rather than by value. This means that what you’re really passing is a pointer to the memory address that stores that value, rather than just the value itself. So when the function changes that value we can see the new value after the call.
The way LibFunc handles this is that it puts each parameter value into an array, using the name of the return variable specified. So if you specify the return value as “result” and the function takes 3 parameters LibFunc would return result, result_1, result_2, and result_3 where result contains the function result and result_1 to result_3 contain the values of the passed parameters which might have changed if the function modifies them.
Here’s an example of a Windows API function which returns data in a passed parameter:
http://msdn.microsoft.com/en-us/library/ms724373(VS.85).aspx
UINT WINAPI GetSystemDirectory( __out LPTSTR lpBuffer, __in UINT uSize );
Note that the first parameter has the word “out” in front of it. This signifies that its value is set by the function. The function also returns an integer. If we read the docs we see that the function writes the system directory in lpBuffer and returns the number of characters written to lpBuffer.
So I can use the following code to get the system directory:
LibFunc>Kernel32,GetSystemDirectoryA,dir,buffer,255 MidStr>dir_1,1,dir,sys_dir MessageModal>System Directory: %sys_dir%
Note that I’ve set the return variable to “dir”. We can pass any old value in buffer, but I’ve used “buffer” here to make it obvious what it does. Remember that LibFunc creates an array named after the result variable. So we get “dir” containing the number of characters written to the buffer, dir_1 containing the buffer itself and dir_2 will just be 255 because that’s what we passed in but isn’t changed by the function as it is an “__in” parameter.
We set the maximum buffer size to 255. So we need to extract only the characters returned, which is the reason why the function tells you how many characters it output. So I’ve used MidStr to extract only those characters from the returned buffer.
Windows Constants
Many times we need to know the value of a Windows Constant. The documentation for a function may refer to the name of a constant you need to use with the function. E.g.:
ShowWindow
http://msdn.microsoft.com/en-us/library/ms633548(VS.85).aspx
BOOL ShowWindow( HWND hWnd, int nCmdShow )
The docs say that nCmdShow specifies how the window is to be shown and says it can be one of the following values: SW_FORCEMINIMIZE, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE and so on. These are Windows Constants.
In Windows development languages such as C++ and Delphi these constants are defined in the header files. In Macro Scheduler they are not defined, so we need to define them ourselves:
Let>SW_RESTORE=9
But, I hear you ask, how do I know that SW_RESTORE’s value is 9? Well, if you have a development environment like Visual Studio or Delphi installed you can find out by searching the header files.
However, if you don’t have this facility there’s a very handy free tool from Microsoft snappily titled “P/Invoke Interop Assistant” which contains a database of Windows functions and constants you can search. You can download it from:
http://www.codeplex.com/clrinterop
Under the “SigImp Search” tab enter the constant you are looking for and it will tell you its value.
The Windows header files give the constant values in hexadecimal, so if obtaining them from the header files you will need to convert to decimal. The Windows Calculator is handy for doing this. “P/Invoke Interop Assistant” also shows the values in hexadecimal, but if you click the “Generate” button it will create C# or VB code with the value declared as an integer.
STDCALL Calling Convention
Finally, a note about calling conventions. When a DLL is created the programmer can decide in what order parameters should be passed to the functions and who should clean up afterwards. Windows API functions use the “stdcall” calling convention in which arguments are passed from right to left and the callee, i.e. the DLL, is responsible for cleaning the stack. This therefore is the calling convention supported by LibFunc. You don’t need to worry about this when calling Windows API functions. But if you come to working with third party or custom DLLs, or ones you have created yourself, you will need to make sure the DLL uses the stdcall convention.
April 13, 2009
We can Automate Your Task
We don’t just sell great automation software. We can also build your task for you.
If you don’t have the time, know-how or technical resource to automate your task yourself you might want to check out our new We Can Build It service.
We’ve been doing custom script development for years but have never actively promoted it as a separate service. Now, with our We Can Build It service we can take a look at your process and automate it for you.
Many people reading this blog are already using and benefiting from Macro Scheduler and happily automating their tasks themselves. If you’re one of those people you probably don’t need this service, but you may know someone else who does. If so please pass this post on to them.
For more information or to find out if your task is a candidate for automation click here.
March 12, 2009
IsNumeric: More fun with Easy Patterns
VBScript has a function called IsNumeric, but as discussed in this forum post VBScript numbers can contain the letter ‘E’ (and it would seem ‘D’ too). These are valid numbers as far as VBScript is concerned but in the real world we usually don’t care for them. Numbers may also be floats. So in the forum post we came up with the following VBScript functions for IsNumber and IsInteger:
VBSTART Function IsNumber(var) IsNumber = (LCase(var) = UCase(var)) and isNumeric(var) End Function Function IsInteger(var) IsInteger = IsNumber(var) and (InStr(var,".") = 0) End Function VBEND
Now we have Easy Patterns I thought I’d show you another neat way to validate that a string is numeric:
//IsNumeric? Let>data=154.3 RegEx>[lineStart][oneOrMore number or "."][lineEnd],data,1,matches,num_matches,0 If>num_matches>0 //string IS numeric Endif
If you just want to check the string is integer, change it to:
//IsInteger? Let>data=154 RegEx>[lineStart][oneOrMore number][lineEnd],data,1,matches,num_matches,0 If>num_matches>0 //string IS integer Endif
If we want to see if a string merely contains a number we can do:
//Contains a number? Let>data=fred 224 sally 4 RegEx>[oneOrMore number],data,1,matches,num_matches,0 If>num_matches>0 //string CONTAINS number Endif
Reading from CSV Files
An interesting discussion arose in the forums yesterday on how to read data from a CSV file. The usual suggestion is to use Separate to split each line into an array using the comma as the delimiter, as explained in my post here.
The problem, as highlighted in the forum topic, is: what if the line has a field which itself contains a comma? In CSV if a field contains a comma it will be encased in double quotes. E.g.:
Sally,”2,500″,Blue
If we just use Separate using the comma as a delimiter we will end up with the following values:
Sally
“2
500”
Blue
Of course this isn’t what we want. We want to end up with:
Sally
2,500
Blue
In the forum discussion a number of suggestions were made such as to use regular expressions to find the fields inside quotes and replace the commas with something else, then use Separate and then put the commas back. Certainly we could resort to some kind of text parsing and I’ve updated the post with a RegEx solution.
However, we can avoid all this by using DBQuery and an ADO connection string that lets us connect to and query a CSV file and treat it like a database table. All the work is done for us:
Let>ConStr=Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\My Documents;Extended Properties='text;HDR=NO;FMT=Delimited' DBConnect>ConStr,dbH DBQuery>dbH,select * from test.csv,fields,num_recs,num_fields
In the connection string specify the path of the CSV file in Data Source. We can also tell it whether or not the CSV file has a header line by setting HDR to NO or Yes.
This reads all the data from the CSV file into an array.
Here’s some code to read through the array and display each field one by one:
Let>rec=0 Repeat>rec Let>rec=rec+1 Let>field=0 Repeat>field Let>field=field+1 Let>this_field=fields_%rec%_%field% MessageModal>this_field Until>field=num_fields Until>rec=num_recs
If the CSV file has a header line change HDR to Yes and then we could also tell DBQuery to return the field names:
Let>ConStr=Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\My Documents;Extended Properties='text;HDR=NO;FMT=Delimited' DBConnect>ConStr,dbH DBQuery>dbH,select * from test.csv,fields,num_recs,num_fields,1
Then we can access the data using the field names. E.g.:
Let>rec=0 Repeat>rec Let>rec=rec+1 Let>name=fields_%rec%_name Let>number=fields_%rec%_number Let>colour=fields_%rec%_colour MessageModal>%name% %number% %colour% Until>rec=num_recs
For more help with Macro Schedulers database functions see:
Using Macro Scheduler’s Database Functions
February 26, 2009
Sneak Peak: Simplified Regular Expression Support
I don’t know many people who find Regular Expressions easy. If the following makes no sense to you, don’t worry, you’re not alone:
([a-z0-9!#$%&’*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&’*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)
It’s actually a regular expression pattern which will match an email address in a string. I’m sure you knew that.
At present to use Regular Expressions in Macro Scheduler you have to use VBScript’s regular expression object:
VBSTART Function RegExpTest(sEmail) RegExpTest = false Dim regEx, retVal Set regEx = New RegExp regEx.Pattern ="([a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)" regEx.IgnoreCase = true Set retMatches = regEx.Execute(sEmail) If retMatches.Count > 0 then RegExpTest = retMatches(0).Value End If End Function VBEND VBEval>RegExpTest("My email address is: [email protected]"),theEmail MessageModal>theEmail
In order to simplify things we’re currently working on a native Regular Expression function called, appropriately enough, RegEx. Using this, the following code will find the email address in the given string:
Let>text=My Email Address: [email protected] Let>pattern=([a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?) RegEx>pattern,text,0,matches,num,0 MessageModal>matches_1
A bit simpler, as you don’t need to use VBScript. But you still need to use that weird and wonderful regular expression syntax.
Luckily our friends at DataMystic have created something called EasyPatterns which maps real English to regular expression syntax. Thanks to DataMystic we are able to use EasyPatterns in Macro Scheduler. Setting the EasyPatterns flag in the new RegEx command allows us to turn the above into:
Let>text=My Email Address: [email protected] Let>pattern=[EmailAddress] RegEx>pattern,text,1,matches,num,0 MessageModal>matches_1
Note the second line has been simplified to Let>pattern=[EmailAddress]. Nice. Now it makes sense!
Check out the EasyPatterns Reference here to find out what else you can do.
Watch out for the next Macro Scheduler maintenance update, which, all being well, will have this function included as a bonus.
Please note this post refers to work in progress. The syntax in the released version may differ slightly. I will update with changes when they happen.