Share |

Marcus' Macro Blog

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

Archive for the ‘Scripting’ Category



How long does xyz take?

Friday, February 3rd, 2012

Need to measure how long a process or a set of commands in your script takes?

We used to have to do this with VBScript’s Timer function. As of v13 Macro Scheduler has it’s own Timer function.

Timer>result

Returns the number of milliseconds that have elapsed since the script was started.

So let’s say we want to measure how long a script takes to run. We could use Timer at the top of the script to get the current milliseconds value, then again at the end and take one from the other to get the elapsed duration in milliseconds:

Timer>startTime
..
.. some code here
..
Timer>endTime
Let>elapsed_seconds={(%endTime%-%startTime%)/1000}
MessageModal>Seconds Elapsed: %elapsed_seconds%

Get Internet IP Address

Wednesday, June 29th, 2011

If you are connected to the Internet here’s an easy way of getting your public IP address by visiting checkip.dyndns.org:

HTTPRequest>http://checkip.dyndns.org/,,GET,,HTMLResponse
RegEx>[IPAddress],HTMLResponse,1,ips,num_ips,0
If>num_ips>0
  MessageModal>Your IP is %ips_1%
Else
  MessageModal>Error retrieving IP from checkip.dyndns.org
Endif

Calling Macro Scheduler Functions from PowerShell

Monday, June 13th, 2011

Further to my post about the MacroScript SDK the other day, here’s an example of loading the MacroScript SDK COM object from PowerShell and running some Macro Scheduler code:

  $objMS = new-object -comobject "mscript.macroscript"
  $objMS.Init()
  $objMS.RunCode( "Run>notepad.exe" + [Environment]::NewLine +
  		"WaitWindowOpen>Untitled - Notepad" + [Environment]::NewLine +
  		"Wait>0.5" + [Environment]::NewLine +
  		"Send>Hello World", "")
  $objMS.Cleanup()

You will need to put the mscript.dll file into the system path (e.g. System32/SysWow64) or into the PowerShell folder.

Don’t forget that with the MacroScript SDK you can retrieve information and query the data back to PowerShell using the GetVar function (see previous post). So for all you system administrators using PowerShell but needing the GUI automation capabilities of Macro Scheduler as well, the MacroScript SDK is the perfect companion.

Customizing Message/Input Boxes

Wednesday, June 8th, 2011

Every now and then someone asks something like “How do I change the font in a modal dialog box?” or “Can I make an Input box multi-line?”.

Well, no, you can’t do those things to the standard Message/MessageModal or Input box functions. But, don’t forget that with Macro Scheduler you have the ability to create your own dialogs and make them act and feel pretty much any way you like. So the answer to the above questions, is “Create your own versions”.

As an example, let’s say you want a modal dialog that looks and acts much like the standard modal message box created by MessageModal. Only you want the text to be green in an italicized aerial font. Here you go:

//this would go at the top - customize as you wish
Dialog>CustomMsgBox
object CustomMsgBox: TForm
  Left = 493
  Top = 208
  HelpContext = 5000
  BorderIcons = [biSystemMenu]
  BorderStyle = bsSingle
  Caption = 'My Message'
  ClientHeight = 170
  ClientWidth = 319
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = True
  ShowHint = True
  OnTaskBar = False
  PixelsPerInch = 96
  TextHeight = 13
  object MSMemo1: tMSMemo
    Left = 0
    Top = 0
    Width = 321
    Height = 137
    Font.Charset = ANSI_CHARSET
    Font.Color = clGreen
    Font.Height = -11
    Font.Name = 'Arial'
    Font.Style = [fsBold, fsItalic]
    ParentFont = False
    TabOrder = 0
  end
  object MSButton1: tMSButton
    Left = 121
    Top = 143
    Width = 75
    Height = 25
    Caption = 'OK'
    DoubleBuffered = True
    ModalResult = 2
    ParentDoubleBuffered = False
    TabOrder = 1
    DoBrowse = False
    BrowseStyle = fbOpen
  end
end
EndDialog>CustomMsgBox

SRT>ShowMsg
  SetDialogProperty>CustomMsgBox,,Position,poScreenCenter
  SetDialogProperty>CustomMsgBox,MSMemo1,Text,ShowMsg_Var_1
  Show>CustomMsgBox,r
END>ShowMsg

//do this to call your message
Let>MyMsg=Hello world, this is a lovely custom message box
GoSub>ShowMsg,MyMsg

And don’t forget that once created you can call the dialog any time you like. And if you want to use it in lots of scripts then put the dialog block and subroutine into an include file and use Include> at the top of each script you want to use it in.

Now, it’s over to you. Use your imagination and style the dialog any way you like. We have some Custom Dialog tutorials here: Part1, Part2

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!

Undocumented Internal Dialog Event Parameters

Tuesday, February 8th, 2011

In this forum post Armsys asks how he can determine which key the user pressed in an OnKeyPress dialog event handler. The solution I posted reveals an undocumented feature: Internal event parameters.

While there is a sample macro called “Dialogs – MouseOver” which ships with Macro Scheduler and demonstrates these event parameters, they are missing from the help file.

So here’s a short 3 minute video showing how this sample script works and demonstrating how you can determine what event parameters are available for use.

(Don’t forget you can view full screen and/or change the quality with the options in the video control panel above).

If you’re completely new to custom dialogs you might also want to watch part 1 and part 2 of the custom dialog video tutorials first.

Slow Mouse Move

Tuesday, January 18th, 2011

A support request came in today asking how to show the mouse moving from one point to another slowly enough to be visible in a video demo.

The regular MouseMove function simply “jumps” the mouse cursor straight to the given point, without passing any points between wherever it was to start with and that end point. So the question was how to specify a start point and an end point and show the mouse moving on a line between them.

To achieve this we need to determine what that line is. We don’t want to move to *every* point between those two points on both the x and y axis. So how do we do it?

Well, it was a long time ago but deep in the recesses of my mind was a little equation we learnt in mathematics at school. I admit I had to look it up. It was the “slope-intercept” equation:

y = mx + b

The slope “m” is the change in y over the change in x:

m = (y2 – y1) / (x2 – x1)

Once we have that we can calculate b (the y-intercept) given a known point (e.g. the start point) and then for each x we can calculate y.

So here’s a script which will move the mouse slowly through the line between two given points:

Let>startX=20
Let>startY=100
Let>endX=300
Let>endY=400
Let>delay_interval=0.001

//remember school math?
//equation of a line is y = mx+b

//m = y2-y1 / x2-x1
Let>m={(%endY%-%startY%)/(%endX%-%startX%)}
//b = y-mx
Let>b={%startY%-(%m% * %startX%)}

Let>x=startX
Let>y=startY
Repeat>x
   //y = mx+b
   Let>y={Trunc((%m% * %x%) + %b%)}
   MouseMove>x,y
   Wait>delay_interval
   If>endX>startX
     Let>x=x+1
   Else
     Let>x=x-1
   Endif
Until>x=endX

This probably isn’t much use to most people, but it’s a little bit of fun. Maybe you can jazz up your scripts to animate the movement of the cursor or something :-)

Doing anything a little bit out of the ordinary with Macro Scheduler? Let me know.

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.

Creating Native Function Aliases for Win32 Functions

Monday, November 15th, 2010

The other day I posted a response on the forums which uses LibFunc to run the Win32 API function GetKeyState. This prompted someone to email me the following:

I didn’t know the Win API call to check for a key being down. The API call works but have you considered adding a native MS command called KeyDown> or maybe CheckKeyDown>

Well, it’s already possible to create native looking functions for DLL functions and therefore Win32 API functions.

We can do this by creating an Imports file in the Imports sub folder of Macro Scheduler. Let’s make an import file for some functions in User32.dll:

Step 1. If you don’t already have an Imports subfolder inside the Macro Scheduler program folder, create one: c:\program files\Macro Scheduler12\Imports

Step 2. Create a symbolic link to the appropriate DLL in this folder. Start a CMD prompt and CD to the Imports folder and for User32.dll type the following:

mklink user32.dll c:\windows\system32\user32.dll

If on a 64 bit system replace system32 with syswow64

This will create a link to the DLL in the Imports folder. Next we need to make a corresponding INI file.

Step 3. Create a file called User32.ini in the Imports folder. Inside this file create entries for each function you wish to import. E.g.:

[GetKeyState]
FunctionName=GetKeyState
Parms=1
Parm1=INT

[SendMessage]
Parms=4
Parm1=INT
Parm2=INT
Parm3=INT
Parm4=INT

[FindWindow]
Parms=2
Parm1=PCHAR
Parm2=PCHAR

Now, instead of writing:

Let>VK_UP=38
LibFunc>user32,GetKeyState,result,VK_UP

You can write:

Let>VK_UP=38
GetKeyState>VK_UP,result

So here’s a project for someone or a group of people: Create a User32.ini file for a fuller list of compatible functions which we can share on the website. Any function that accepts either integers (INT) or strings (PCHAR) and returns an integer (or nothing) can be included. And then we want another one for Kernel32.dll … etc.