Share |

Marcus' Macro Blog

Mostly tips, tutorials, articles and news about Macro Scheduler & Windows Automation

Archive for the ‘Automation’ Category



WebRecorder 3.0 – Faster, More Reliable, More Functions

Tuesday, January 24th, 2012

We have today released WebRecorder 3.0.

WebRecorder 3.0 - Faster more reliable internet macros

This is a major rewrite with a better recording engine capable of recording more tags and producing leaner, cleaner, code and a faster and more reliable runtime.

It also introduces a download manager so that file downloads can be recorded and scripted more easily without recourse to sending keystrokes to IE’s standard file download box.

We’ve improved the existing commands so that you can script more tags and use more attributes and added new functions for retrieving data, retrieving the screen position of elements and interfacing with the new native IE functions in Macro Scheduler 13.1.

Oh, and you no longer need to use the mouse to record clicks and form fills!

  • Cleaner, leaner, faster code.
  • More reliable method for waiting for documents to complete loading
  • Download manager for scripting file downloads
  • Better error trapping
  • Ability to set timeout for Clicks and Form Fills etc
  • Click and Form fill functions will wait until target tags exist (within timeout)
  • New functions for extracting data
  • New function to get X,Y screen position of elements
  • No need to use the mouse to identify recorded elements
  • New, improved UI look and feel
  • Runtime component integrates better with Macro Scheduler’s script controller (faster termination, less liable to hang ups when aborting scripts etc)
  • Interface easily with Macro Scheduler’s IEGetTags and IETagEvent function
  • New script function for waiting for text in the page before continuing
  • Functions to automatically set extracted tag buffer size and avoid buffer overruns

More information on WebRecorder here.  WebRecorder 3.0 scripts require Macro Scheduler 13.1 to run.

Enterprise customers with in-date maintenance can already download WebRecorder 3.0 from the registered download area.

Other customers can view upgrade options in their registered download account, or download a trial version here.

Trial Downloads | Registered Downloads | Upgrades

Scraping Data From Web Pages

Friday, May 20th, 2011

I’ve seen quite a lot of requests lately from people wanting to know how to extract text from web pages.

Macro Scheduler’s optional WebRecorder add-on simplifies the automation of web pages and includes functions for extracting tables, text or HTML from web page elements. WebRecorder’s Tag Extraction wizard makes it easy to create the code.

Sometimes you can choose a specific HTML element and identify it uniquely via it’s ID or NAME attribute. But other times you might want all the text from the whole page, or you may need to extract the entire page and then parse out the bits you’re interested in using RegEx or some other string manipulation functions.

To extract an entire page I specify the BODY element. If you want to extract data from web pages it does help if you know a little about HTML. And if you do you’ll know that each page has just one BODY element which contains the code making up the visible portion of the page.

Here’s code produced using WebRecorder when navigating to mjtnet.com and using the Tag Extraction wizard to extract the BODY text:

IE_Create>0,IE[0]

IE_Navigate>%IE[0]%,http://www.mjtnet.com/,r
IE_Wait>%IE[0]%,r
Wait>delay

//Modify buffer size if required (you may get a crash if buffer size too small for data) ...
Let>BODY0_SIZE=9999
IE_ExtractTag>%IE[0]%,,BODY,0,0,BODY0,r
MidStr>r_6,1,r,BODY0

MessageModal>BODY0

The macro simply displays just the text in a message box but could be set to pull out the full HTML. You could then parse it with RegEx to get the information you are interested in.

You will need WebRecorder installed for the above to work.

If you don’t have WebRecorder you can do the same with a bit more work using VBScript. Some library functions for doing this can be found here and here.

So here’s the equivalent in VBScript:

VBSTART
Dim IE

'Creates IE instance
Sub CreateIE
  Set IE = CreateObject("InternetExplorer.Application")
  IE.Visible=1
End Sub

'Navigate to an IE instance
Sub Navigate(URL)
  IE.Navigate URL
  do while IE.Busy
  loop
End Sub

'This function extracts text from a specific tag by name and index
'e.g. TABLE,0 (1st Table element) or P,1 (2nd Paragraph element)
'set all to 1 to extract all HTML, 0 for only inside text without HTML
Function ExtractTag(TagName,Num,all)
  dim t
  set t = IE.document.getElementsbyTagname(Tagname)
  if all=1 then
    ExtractTag = t.Item(Num).outerHTML
  else
    ExtractTag = t.Item(Num).innerText
  end if
End Function
VBEND

VBRun>CreateIE
VBRun>Navigate,www.mjtnet.com

VBEval>ExtractTag("BODY",0,0),BodyText
MessageModal>BodyText

But what if you already have a macro which already opens IE, or works against an already open instance of IE? The above macros need to create the IE instance before they can access them and extract data from them. You may have a macro that already starts IE some other way – maybe just by using a RunProgram or ExecuteFile call, or indirectly via some other application. Many times people tackle the extraction of data from such an IE window by sending keystrokes to do a Select-All, Edit/Copy and then use GetClipboard; or even File/Save As to save the HTML to a file. This of course adds time and can be unreliable. So how else can we do it?

Well, this tip shows us a function we can use to attach to an existing IE instance. So let’s use that and then use our ExtractTag function to pull out the BODY HTML:

VBSTART
Dim IE

' Attaches to an already running IE instance with given URL
Sub GetIE(URL)
  Dim objInstances, objIE
  Set objInstances = CreateObject("Shell.Application").windows
  If objInstances.Count > 0 Then '/// make sure we have instances open.
    For Each objIE In objInstances
      If InStr(objIE.LocationURL,URL) > 0 then
        Set IE = objIE
      End if
    Next
  End if
End Sub

'This function extracts text from a specific tag by name and index
'e.g. TABLE,0 (1st Table element) or P,1 (2nd Paragraph element)
'set all to 1 to extract all HTML, 0 for only inside text without HTML
Function ExtractTag(TagName,Num,all)
  dim t
  set t = IE.document.getElementsbyTagname(Tagname)
  if all=1 then
    ExtractTag = t.Item(Num).outerHTML
  else
    ExtractTag = t.Item(Num).innerText
  end if
End Function
VBEND

VBRun>GetIE,www.mjtnet.com

VBEval>ExtractTag("BODY",0,1),BodyHTML
MessageModal>BodyHTML

This snippet assumes a copy of IE is already open and pointing to www.mjtnet.com. The GetIE call creates a link to that IE window and then we use the ExtractTag function to pull out the HTML of the BODY element.

These examples use the BODY element, which will contain everything displayed on the page. As I mentioned before you can be more specific and specify some other element, and with WebRecorder, or a modified version of the ExtractTag VBScript function use other attributes to identify the element (the existing VBScript ExtractTag function shown above just uses the numeric index). WebRecorder tries to make it simple by giving you a point and click wizard, making some assumptions for you, so that you need not fully understand the HTML of the page. But it still helps you understand HTML. Looking at the source of the page you should be able to identify the element you need to extract from. And whether you extract directly from that or extract the BODY and then use RegEx being prepared to delve into the HTML source is going to get you further.

UPDATE: 19th January 2012

As of version 13.0.06 Macro Scheduler now includes a function called IEGetTags. For a given tag type and IE tab this will retrieve an array of tag contents. It can extract just the text, or html of the tags. This example extracts the inner HTML of all DIV elements in the open IE document currently at www.mjtnet.com:

IEGetTags>mjtnet.com,DIV,H,divArr

You can then cycle through each one with a Repeat Until

If>divArr_count>0
  Let>k=0
  Repeat>k
    Let>k=k+1
    Let>this_div_html=divArr_%k%
    ..
    .. do something with it
    .. e.g. use RegEx or substring searching to determine
    .. if this is the DIV you want and extract from it
    ..
  Until>k=divArr_count
Endif

To further identify the tag you are interested in, or find the data you want, you can use RegEx, EasyPatterns, or string functions.

Macro Scheduler 13.0.06 and above also has a function called IETagEvent which will let you simulate a Click on a given tag, focus it, or modify its value. So once you have identified a tag using IEGetTags and your Repeat/Until loop you can click on it, focus it or modify its value (e.g. for form fields).

New Video: Using The Debugger

Wednesday, February 23rd, 2011

Macro Scheduler veteran John Brozycki has put together this fantastic video tutorial all about Macro Scheduler’s debugging capabilities. The video is 18 minutes long and demonstrates every debug feature, showing examples of their use and talks about how useful the debugger can be for problem resolution as well as script creation. Take a look:

A larger version of the video can be found here.

John Brozycki is an information security professional who uses Macro Scheduler as a tool to accomplish a wide range of tasks in his daily activities. His personal web site is www.trueinsecurity.com.

I think this is an excellent tutorial which all script developers should benefit from. Thanks John!

Mixing the Native Excel Functions with VBScript

Wednesday, December 15th, 2010

Macro Scheduler comes complete with some native functions for controlling Excel, such as XLOpen, XLGetCell, XLSetCell and others. Obviously, although we intend to add more functions over time, not every possible Excel function has been duplicated. So sometimes you may want to utilise COM via VBScript which allows you to access the entire Excel API. There are plenty of examples of this here in the blog and on the forums.

But what if you want to use a combination of both? You might already have a script which uses the native XL functions to open a sheet and get or set some data. Let’s say you now want to augment this with an Excel method which is not exposed by the native functions. Rather than re-writing your entire script to use VBScript, is there a way we can let VBScript take over?

While it’s not possible to share native XL references with VBScript object references, what we can do is have VBScript attach to an open instance of Excel using the GetObject function. So sometime after running XLOpen we could then run a VBScript function which does a GetObject to get an object reference to Excel and then after that we are able to utlise any Excel function we like via VBScript.

The following script demonstrates:

VBSTART
  Dim xlApp
  Dim xlBook
  Sub GetXL
    Set xlApp = GetObject(,"Excel.Application")
    Set xlBook = xlApp.ActiveWorkbook
  End Sub

  Function FindCell(Sheet,Data)
    Dim theCell
    Dim xlValues
    xlValues = -4163

    Dim xlSheet
    Set xlSheet = xlBook.Worksheets(Sheet)
    xlSheet.Range("A1").Select
    Set theCell = xlSheet.Cells.Find(Data, xlApp.ActiveCell, xlValues)
    FindCell = CStr(theCell.Row) & ":" & CStr(theCell.Column)
  End Function
VBEND

//Open an XLS file natively
XLOpen>%SCRIPT_DIR%\example.xls,1,xlH

//Call GetXL to give VBScript a reference to the XL instance
VBRun>GetXL

//now we can access any XL function via VBScript
VBEval>FindCell("Sheet1","Price"),res

The only thing to be careful of is that there are no existing copies of Excel open before the one opened by XLOpen because according to the Microsoft docs GetObject will attach to the first opened instance. You could of course make the script check for this.

Video Tutorial – Macro Scheduler for Non-Technical Beginners – Part 2

Thursday, December 2nd, 2010

Another contribution from Peter Begelsdorf which takes off from where his last video left off. Aimed at complete beginners this video demonstrates how to execute a file and wait for it to be ready before sending text and keystrokes to it.

Part 1 of this sequence and other videos can be found on the Video Tutorials Page.

This video is a result of my recent offer for free licenses and updates in exchange for video tutorial contributions. If you want to have a go at creating a tutorial please let me know.

Is a File Ready? Waiting for a File Process to Complete.

Wednesday, November 3rd, 2010

In the comments to my last post Richard asked how we can wait for a process to finish updating a file.

In my reply to Richard I point out that most of the time we can use a visual cue. Where possible this is the approach I would use. Usually an application would present some kind of visual cue when the file operation finishes. A dialog box might appear, or disappear, an object may become enabled or disabled, some text may appear on the screen, etc. We might be able to use commands such as WaitWindowOpen, WaitWindowClosed, WaitWindowChanged, WaitScreenText, WaitScreenImage, WaitCursorChanged, WaitPixelColor, or WaitRectChanged. Or we might even create our own wait loop which checks for some value or property. I mention this approach in Top Tips for Reliable Macros.

Unfortunately there are sometimes scenarios where there is no visual cue and we have nothing we can “see” which tells us the file operation has completed.

If the file is locked by the application that is writing to it, we could create a wait loop which checks to see if the file is still locked or not.

How might we achieve that? Well, why not just try to read from the file. If the file is locked we’re not going to be able to read it right? So we could just do:

Label>wait_file
ReadLn>file,1,res
Pos>##ERR##,res,1,pErr
If>pErr>0
  Wait>0.2
  Goto>wait_file
Endif

This will work for any kind of file.

Of course this assumes that the file is locked for reading by other processes.

It’s possible that the application locks the file only for writing. In that situation we could use WriteLn to attempt to write an empty string to the file:

Let>WLN_NOCRLF=1
Label>wait_file
WriteLn>file,res,{""}
If>RES<>0
  Wait>0.2
  Goto>wait_file
Endif

But that’s a bit invasive in the instance that the file IS available as although we’re writing nothing, we’re still modifying it so updating it’s timestamp.

We can get a bit more control over this with VBScript where we can attempt to open a file for appending without actually modifying it:

VBSTART
Function TestFile(filename)
   Set oFSO = CreateObject("Scripting.FileSystemObject")
   On Error Resume Next
   Set f = oFSO.OpenTextFile(filename, 8, True)
   TestFile = Err.Number
End Function
VBEND

Let>file=C:\files\bmp.bmp

Label>wait_file
VBEval>TestFile("%file%"),fRes
If>fRes<>0
  Wait>0.2
  Goto>wait_file
Endif

All the TestFile function does is try to open the file for appending, and if an error occurs it returns the error code. So if we get zero back, there’s no error and the file must be available for writing and therefore not locked.

Don’t be put off by the fact that this uses the OpenTextFile method. It will still work with any kind of file. We’re not actually going to attempt to modify it. We’re just trying to see if we can open it. So the file type is unimportant.

Note that in these samples I haven’t checked for any specific error code nor have I checked for the existence of the file first – I’m assuming any error means the file isn’t available. An error will also be returned if the file does not exist, so you’d probably want to use IfFileExists first. You might even want to put FileExists in a loop so that you can wait until it exists.

Note also the small wait in each loop. This is just to make sure the loop isn’t so tight that it hogs CPU. Better to be nice to the system and give other processes a chance to breathe.

Waiting for the Clipboard

Tuesday, November 2nd, 2010

Today I was helping someone who was wanting to write a script to take screen-shots from one application and then paste those screen-shots into Microsoft Excel.

Initially things weren’t working reliably because the script didn’t factor in the time taken for the large bitmap of a screen-shot to exist in the clipboard after pressing the print screen button, before attempting to paste into Excel. E.g. consider:

Press Print Screen
SetFocus>Microsoft - Excel*
Press CTRL
Send>v
Release CTRL

The above is probably going to fail most of the time because a screen shot is a large bitmap and is going to take some time to arrive on the clipboard, but the script above performs a paste in Excel immediately after pressing print screen. The print screen key being depressed and the clipboard containing the bitmap are not the same thing.

While we could have just said “wait 5 seconds” and that would probably have been fine for evermore, it isn’t very sensitive and wouldn’t be ideal for a script that needs to run as fast as possible. Ideally we only want to wait until we know the bitmap is in the clipboard.

Text is usually smaller than a bitmap, but for large text items one way to make things bulletproof is to do something like this:

PutClipBoard>dummy

SetFocus>source_app_title
Press CTRL
Send>c
Release CTRL

Label>wait_for_data
Wait>0.2
GetClipBoard>clipdata
If>clipdata=dummy
  Goto>wait_for_data
Endif

By putting a known value onto the clipboard in the first place we can then have a little loop which keeps checking the clipboard until the returned value is not our known value. We then know our CTRL-V has worked and that we can safely paste to the target application.

But how can we do the same thing when the clipboard data is an image? The above won’t work because GetClipBoard won’t return anything for non-textual data.

Well, Windows has a function called IsClipboardFormatAvailable which will allow us to determine what kind of data is on the clipboard. So we could use this in a similar way to above to see if the clipboard contains a bitmap or not. Like this:

Let>CF_BITMAP=2

Let>haveBMP=0
PutClipBoard>dummy

Press Print Screen

While>haveBMP=0
  LibFunc>user32,IsClipboardFormatAvailable,haveBMP,CF_BITMAP
  Wait>0.2
EndWhile

We could then paste it somewhere:

SetFocus>Document - WordPad
Press CTRL
Send>v
Release CTRL

It’s always nice to wait only as long as we have to and it makes the script more reliable and portable.

Making a Dialog or Window Stay On Top

Thursday, September 16th, 2010

Edit: 23 March 2011. In Version 12 it is possible to make a dialog stay on top simply by setting the dialog’s FormStyle property to fsStayOnTop. The method outlined in this post is not necessary for v12 dialogs but will remain as it can be used for older versions and the same approach can be used for windows belonging to other processes.

A question that comes up every now and then is how to make a custom dialog stay “on top” of other windows even when it loses the focus. There is a way to do this by calling the Windows API function SetWindowPos. There are examples in the forums but for convenience here’s a version of one:

SRT>StayOnTop
  Let>HWND_TOPMOST=-1
  Let>HWND_NOTOPMOST=-2
  Let>SWP_NOSIZE=1
  Let>SWP_NOMOVE=2
  Let>SWP_NOACTIVATE=16
  Let>SWP_SHOWWINDOW=64
  Let>WindowHandle=%StayOnTop_var_1%
  Let>Flags={%SWP_NOACTIVATE% Or %SWP_SHOWWINDOW% Or %SWP_NOMOVE% Or %SWP_NOSIZE%}
  LibFunc>User32,SetWindowPos,swpr,%WindowHandle%,HWND_TOPMOST,0,0,0,0,Flags
END>StayOnTop

This subroutine takes a window handle and modifies the properties of that window to make it stay above other windows.

For example, if we had just opened Notepad and wanted to force it to stay on top we could do:

GetWindowHandle>Untitled - Notepad,hwndNotepad
GoSub>StayOnTop,hwndNotepad

Or to get the handle of the active window use GetActiveWindow with WIN_USEHANDLE set to 1.

To set one of your own custom dialogs to stay on top use:

GoSub>StayOnTop,DialogName.Handle

Don’t forget that we are at the mercy of Windows here (and possibly the developers of the app we’re trying to force to the top). It’s a bit rude to have a window floating around on top of other windows. And consider what would happen if another window is opened which is also set to stay on top in the same way? You can’t have two windows on top. So multiple “stay on top” windows will still overlap each other depending on which one has the focus. In short you can’t really guarantee that a window will always be on top.

Accessing 64 bit System Folders – Turn off File System Redirection

Wednesday, September 15th, 2010

If you need to run a 64 bit system command, or a system command that requires access to the 64 bit system folders you may need to turn off File System Redirection.

To turn off File System Redirection call the snappily named Wow64DisableWow64FsRedirection function and remember to turn it back on with the equally memorable function Wow64RevertWow64FsRedirection.

For example:

//turn off File System Redirection
LibFunc>kernel32,Wow64DisableWow64FsRedirection,result,0

Let>RP_ADMIN=1
let>RP_WAIT=1
DeleteFile>%TEMP_DIR%\vss.txt
Run>"cmd.exe" /c vssadmin list shadowstorage  >> "%TEMP_DIR%\vss.txt"
ReadFile>%TEMP_DIR%\vss.txt,vss
MessageModal>vss

//revert File System Redirection
LibFunc>kernel32,Wow64RevertWow64FsRedirection,result,0

Recently someone running the 64 bit version of XP was having a problem running this innocuous looking code:

Run>c:\windows\system32\mstsc.exe

mstsc.exe is the remote desktop client. Macro Scheduler returned an error saying that c:\windows\system32\mstsc.exe could not be found, yet using Windows Explorer we could see mstsc.exe clearly in the system32 folder. The same code worked perfectly fine on a Win7 x64 system.

Eventually I realised what was happening. Because the OS was 64 bit and Macro Scheduler is 32 bit, Windows was redirecting “c:\windows\system32\” to “c:\windows\syswow64″ – the 32 bit system folder. There should be a 32 bit version of mstsc.exe in there, but on this particular system it was missing (it may be that earlier versions of Remote Desktop on x64 did not install a 32 bit version). Hence the error that Macro Scheduler could not find the file. We quickly resolved this by turning off File System Redirection using the code above.

Although the current 32 bit version of Macro Scheduler runs on and is compatible with all 64 bit versions of Windows, a 64 bit version of Macro Scheduler is in the pipeline. Until then calling some system level functions may need the above treatment if those commands need to access the 64 bit subsystem. Macro Scheduler can happily execute and interact with 64 bit applications, but Windows will redirect references to some locations such as system32 unless File System Redirection is disabled.

Trigger Scripts

Wednesday, September 8th, 2010

Macro Scheduler has a number of scheduling features to allow you to specify when a macro should fire. One of these mechanisms is called a Trigger. There are several trigger types:

  • Window Event
  • File Event
  • Folder Event
  • Custom Event

A Window Event can be set to fire when a specified window appears or disappears. Similarly file events can be set to fire the macro when the specified file exists or ceases to exist. Folder events offer much the same (a folder exists or ceases to exist) but also a little more. A Folder event can also be configured which will fire the macro when a new file appears in the folder, or a file is removed from the folder.

New File Triggers

With a “New File in Folder” trigger the macro will fire whenever a new file appears in the folder, regardless of what that file is called. This can be very useful and is often used to detect incoming files for further processing. In a situation like this we would need to detect that a new file has appeared and then do something with it. Using the New File in Folder trigger will cause the macro to run, but then we need the macro to determine what that file is called. We can do that with a VBScript function which returns the newest file in a folder:

VBSTART
Function NewestFile(Folder)
Set oFSO = CreateObject("Scripting.FileSystemObject")
Set oFolder = oFSO.GetFolder(Folder)
dPrevDate = "0"
For Each oFile In oFolder.Files
  If DateDiff("s", dPrevDate, oFile.DateLastModified) > 0 Then
    sNewestFile = oFile.Path
    dPrevDate = oFile.DateLastModified
  End If
Next
NewestFile = sNewestFile
End Function
VBEND

We can then call it like this:

VBEval>NewestFile("c:\downloads"),filename

This will return in filename the path of the newest file in the given folder. So if our macro is fired by a new file appearing in a folder it can then run this code to get that file’s path and do something with it.

Custom Triggers

Custom triggers allow you to create any kind of trigger you can think of by executing Macro Scheduler code. Maybe you want to monitor the contents of a text file or an INI file entry, a registry entry or the size of a specific file. If you can code the check in Macro Scheduler you can create a trigger out of it.

Custom triggers work by linking to a script (.scp) file which contain two subroutines. One subroutine is called Trigger and the other Reset. Initially the Trigger subroutine is called repeatedly until it sets MACRO_RESULT to TRUE. Thereupon Macro Scheduler calls the Reset routine instead until it too sets MACRO_RESULT to TRUE and the cycle then continues.

By way of an example lets say we want to trigger a macro based on the value of an INI file entry. Let’s say our INI file looks like this:

[MyStuff]
Color=red

Let’s say we want to trigger a macro to run when the Color entry in the INI file gets set to “blue”. Here’s our trigger script:

SRT>Trigger
  ReadIniFile>d:\files\myini.ini,MyStuff,Color,gColor
  If>gColor=blue
    Let>MACRO_RESULT=TRUE
  Endif
END>Trigger

SRT>Reset
  ReadIniFile>d:\files\myini.ini,MyStuff,Color,gColor
  If>gColor<>blue
    Let>MACRO_RESULT=TRUE
  Endif
END>Reset

So here the Trigger routine which is executed continually by the scheduler will return TRUE only if Color in the INI file becomes equal to “blue”. Thereupon the Reset routine will be executed which will reset the trigger when Color is no longer “blue”. After that the Trigger routine is executed again.

Since Trigger routines are run frequently on a very tight interval they should be kept as small as possible and should not be long running. Don’t make Trigger scripts that perform too much complicated processing or include delays as this could cause resource issues. Sometimes, for more complicated “triggers” or monitoring scripts you may be better off creating a looping macro or repeating macro. E.g. if you wanted to poll a POP3 server for an email message containing specific content this is a more time consuming process subject to network delays so would probably not be a good candidate for a Custom Trigger. Instead create a macro which checks the email every few minutes, or one that loops continuously.