December 20, 2018

Season’s Greetings!

Filed under: Announcements,General — Marcus Tettmar @ 1:01 pm

From all of us at MJT we wish you season’s greetings and a wonderful start to 2019.

Here’s a fun little script with a message from Macro Scheduler. Open Macro Scheduler (download the trial if you don’t already have it), click New to create a new macro and paste this code in and hit run.

OnEvent>key_down,VK27,0,Quit
SRT>Quit
  SetControlText>Sparkler,TEdit,1,Complete
  WaitWindowClosed>Sparkler
  Wait>1
  DeleteFile>%temp_dir%\Sparkler.scp
  Exit>0
END>Quit
 
DeleteFile>%temp_dir%\Sparkler.scp
LabelToVar>SparkleScript,vScrData
WriteLn>%temp_dir%\Sparkler.scp,wres,vScrData
 
Dialog>Dialog1
object Dialog1: TForm
  BorderStyle = bsNone
  Caption = 'Happy Holidays'
  ClientHeight = 330
  ClientWidth = 780
  Color = 111111
  Position = poScreenCenter
  object Panel3: TPanel
    Left = 0
    Top = 0
    Width = 780
    Height = 330
    BevelEdges = []
    BevelOuter = bvNone
    Caption = 'And a Happy New Year'
    Color = 111111
    Font.Charset = ANSI_CHARSET
    Font.Color = clRed
    Font.Height = -80
    Font.Name = 'Vladimir Script'
    Font.Style = []
    ParentFont = False
    Visible = False
  end
  object Panel2: TPanel
    Left = 0
    Top = 0
    Width = 780
    Height = 330
    BevelEdges = []
    BevelOuter = bvNone
    Caption = 'Merry Christmas'
    Color = 111111
    Font.Charset = ANSI_CHARSET
    Font.Color = clRed
    Font.Height = -96
    Font.Name = 'Old English Text MT'
    Font.Style = []
    ParentFont = False
  end
  object Panel1: TPanel
    Left = 0
    Top = 0
    Width = 780
    Height = 330
    BevelEdges = []
    BevelOuter = bvNone
    Caption = ''
    Color = 111111
  end
end
EndDialog>Dialog1
 
Show>Dialog1
 
ExecuteFile>%temp_dir%\Sparkler.scp
 
Let>WIN_USEHANDLE=1
  GetWindowPos>Dialog1.handle,Dia1X,Dia1Y
Let>WIN_USEHANDLE=0
Add>Dia1Y,165
Let>StartY=Dia1Y
Let>YFlag=1
Wait>0.3
 
Let>kk=0
Repeat>kk
  Add>kk,4
  If>Dia1Y>{%StartY%+20}
    Let>YFlag=0
  EndIf
  If>Dia1Y<{%StartY%-20}
    Let>YFlag=1
  EndIf
  Add>Dia1X,4
  If>YFlag=1
    Add>Dia1Y,8
  Else
    Sub>Dia1Y,8
  EndIf
  Wait>0.025
  If>Dia1X>40
    SetControlText>Sparkler,TEdit,1,%Dia1X%;%Dia1Y%
  EndIf
  SetDialogProperty>Dialog1,Panel1,Left,kk
Until>kk>750
 
Timer>Begin
 
GetWindowPos>Sparkler,SparkX,SparkY
 
Label>Loop
If>{%Dia1X%<%SparkX%+150}
  Add>Dia1X,1
EndIf
If>Dia1Y>StartY
  Sub>Dia1Y,1
EndIf
If>Dia1YDia1Y,1
EndIf
SetControlText>Sparkler,TEdit,1,%Dia1X%;%Dia1Y%
Wait>0.01
Timer>Stop
If>{%Stop%-%Begin%>5000}
  GoSub>Fade
EndIf
Goto>Loop
 
SRT>Fade
  SetDialogProperty>Dialog1,,AlphaBlend,True
  Let>Fader=255
  Repeat>Fader
    Sub>Fader,5
    SetDialogProperty>Dialog1,,AlphaBlendValue,Fader
    Wait>0.1
  Until>Fader<0
  Let>Fader=255
  SetDialogProperty>Dialog1,Panel3,Visible,True
  SetDialogProperty>Dialog1,Panel2,Visible,False
  SetDialogProperty>Dialog1,,AlphaBlendValue,Fader
  Wait>3
  Timer>Begin
  GetScreenRes>ScreenX,ScreenY
  While>{%Stop%-%Begin%<10000}
    Timer>Stop
    Random>100,Pct
    Add>pct,1
    Let>Dia1X={round(%ScreenX%*(%pct%/100))}
    Random>100,Pct
    Add>pct,1
    Let>Dia1Y={round(%ScreenY%*(%pct%/100))}
    SetControlText>Sparkler,TEdit,1,%Dia1X%;%Dia1Y%
    Sub>Fader,5
    SetDialogProperty>Dialog1,,AlphaBlendValue,Fader
    Wait>0.2
  EndWhile
  SetControlText>Sparkler,TEdit,1,Complete
  WaitWindowClosed>Sparkler
  Wait>1
  DeleteFile>%temp_dir%\Sparkler.scp
   
  Exit>0
END>Fade
 
/*
SparkleScript:
Let>size=200
 
OnEvent>key_down,vk27,0,Quit
 
SRT>Quit
  Exit>0
END>Quit
 
Dialog>Dialog2
object Dialog2: TForm
  BorderStyle = bsNone
  Caption = 'Sparkler'
  Color = 1
  TransparentColor = True
  TransparentColorValue = 1
  object Panel1: TPanel
    Left = 0
    Top = 0
    BevelOuter = bvNone
    Caption = 'Panel1'
    Color = 1
    TabOrder = 0
  end
  object Edit1: TEdit
    Text = '-1000;-1000'
    Visible = False
  end
end
EndDialog>Dialog2
 
Let>WIN_USEHANDLE=1
  MoveWindow>Dialog2.handle,-1000,-1000
Let>WIN_USEHANDLE=0
AddDialogHandler>Dialog2,,OnClose,Quit
SetDialogProperty>Dialog2,,ClientHeight,size
SetDialogProperty>Dialog2,,ClientWidth,size
SetDialogProperty>Dialog2,Panel1,Height,size
SetDialogProperty>Dialog2,Panel1,Width,size
SetDialogProperty>Dialog2,,AlphaBlend,True
SetDialogProperty>Dialog2,,AlphaBlendValue,0
Show>Dialog2
 
Let>halfSize={round(%size%/2)}
Let>85Per={round(%halfSize%*0.85)}
Let>15Per={round(%halfSize%*0.15)}
Let>ang2=0
Let>kk=0
SetDialogProperty>Dialog2,,AlphaBlendValue,255
Repeat>kk
  GetDialogProperty>Dialog2,Edit1,Text,vPos
  If>vPos=Complete
    Let>kk=-100
    Goto>Done
  EndIf
  Separate>vPos,;,Cur
  Sub>Cur_1,%halfSize%
  Sub>Cur_2,%halfSize%
  MoveWindow>Sparkler,Cur_1,Cur_2
  Add>kk,1
  Random>85Per,res
  Add>res,%15Per%
  Random>50,color
  Add>Color,45500
  Random>90,ang2
  Let>ang2=%ang2%*4
  GoSub>Angle,Dialog2,Panel1,ang2,%halfSize%,%halfSize%,%halfSize%,4,1
  Random>90,ang3
  Let>ang3=%ang3%*4
  GoSub>Angle,Dialog2,Panel1,ang3,%halfSize%,%halfSize%,%halfSize%,4,1
  Random>90,ang
  Let>ang=%ang%*4
  SetDialogProperty>Dialog2,Panel1,caption,space
  GoSub>Angle,Dialog2,Panel1,ang,%halfSize%,%halfSize%,res,2,color
  Label>Done
Until>kk<0
 
//Angle Usage:
//GoSub>Angle,Dialog,Object,Angle(in degrees),XStart,YStart,Length,PenSize,PenColor
//Requires Drawline subroutine
SRT>Angle
  Let>DegreeAngle=Angle_var_3
  Let>XStart=Angle_var_4
  Let>Ystart=Angle_var_5
  Let>LineLength=Angle_var_6
  Let>RadAngle={%DegreeAngle%*(pi/180)}
  Let>XEnd={trunc((cos(%RadAngle%))*%LineLength%)}
  Let>YEnd={trunc((sin(%RadAngle%))*%LineLength%)}
  Let>XEnd=%XEnd%+%XStart%
  Let>YEnd=%YEnd%+%YStart%
  GoSub>DrawLine,%Angle_var_1%.%Angle_var_2%.Handle,Angle_var_7,Angle_var_8,XStart,YStart,XEnd,YEnd
END>Angle
 
SRT>DrawLine
  LibFunc>user32,GetDC,HDC,%DrawLine_var_1%
  LibFunc>gdi32,CreatePen,Penres,0,%DrawLine_var_2%,%DrawLine_var_3%
  LibFunc>gdi32,SelectObject,SOPres,hdc,Penres
  Libfunc>gdi32,MoveToEx,mtres,HDC,%DrawLine_var_4%,%DrawLine_var_5%,0
  LibFunc>gdi32,LineTo,ltres,hdc,%DrawLine_var_6%,%DrawLine_var_7%
  LibFunc>gdi32,DeleteObject,DOres,Penres
  LibFunc>user32,ReleaseDC,RDCres,HDC_1,HDC
END>DrawLine
*/

Thanks to Dick Lockey for writing this little script (back in 2014).

Remember this?
Case Study: Macro Scheduler Saves 3600 Elf-Hours and Gets Presents Delivered On Time

November 21, 2018

Macro Scheduler 14.5 Available

Filed under: Announcements — Marcus Tettmar @ 8:43 pm

Macro Scheduler build 14.5 is now available for download from the usual places:

Registered Downloads/Upgrades | Trial Versions | New Licenses | Version History

This version adds an improved JSONParse function with improved JSON Path parsing and output of arrays for multiple matches. We’ve also added the long awaited ability to add custom headers to HTTPRequest; the ability to retrieve formulas with XLGetCell instead of just values; support for adding HTML format to the clipboard with PutClipboard and a bunch of other improvements and some small fixes.  For full details see the history list.

July 20, 2018

Cross Language 128 and 256 Bit AES Encryption

Filed under: Scripting,Web/Tech — Marcus Tettmar @ 10:47 am

With Macro Scheduler 14.4.10 we have added new 128 and 256 bit AES Encryption methods which interoperate well with other languages.

Cross-platform/cross-language encryption/decryption can be tricky. Different text encodings, different padding formats and understanding how different algorithms derive keys and initialisation vectors can make encrypting and decrypting between one language and another a bit of a pain!

Macro Scheduler’s AES function originally worked only on Unicode strings as this is the standard string format in Macro Scheduler.  Having had some issues trying to make this work between NodeJS and PHP we decided to create some new UTF8 based AES options (available in Macro Scheduler 14.4.10) which we have verified work well with PHP/OpenSSL and NodeJS.  

These new implementations provide 128 and 256 bit AES encryption.  They use CBC chaining method and use a SHA256 password. If using AES 128 the SHA256 password is truncated to the 32 byte key length.  By default the initialisation vector is set to “0000000000000000” but can be set to whatever you want (but must be 16 characters long). Padding is PKCS#5.

Here is an example of AES_256:

Let>AES_ALG=AES_256_CBC
AESEncrypt>hello world,mypassword,ENCRYPT,result
AESEncrypt>result,mypassword,DECRYPT,original


Here is a compatible PHP example:

data = "hello world";

$method = 'AES-256-CBC';

// simple password hash
$password = 'mypassword';
$key = hex2bin(substr(hash('sha256', $password),0,64));

echo "Method: " . $method . "\n";
$encrypted = encrypt($data, $key, $method);
echo "Encrypted: ". $encrypted . "\n";
$decrypted = decrypt($encrypted, $key, $method);
echo "Decrypted: ". $decrypted . "\n"; // plain text

function encrypt(string $data, string $key, string $method): string
{
  $iv = "0000000000000000";
  $encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
  $encrypted = base64_encode($encrypted);
  return $encrypted;
}

function decrypt(string $data, string $key, string $method): string
{
  $data = base64_decode($data);
  $iv = "0000000000000000";
  $data = openssl_decrypt($data, $method, $key, OPENSSL_RAW_DATA,$iv);
  return $data;
}

Try this PHP Code Here

And here’s the Javascript equivalent:

let data = 'hello world';
let password = 'mypassword';
let iv = '0000000000000000';

let password_hash = crypto.createHash('sha256').update(password,'utf8').digest('hex');

let key = hex2bin(password_hash);
password_hash = Buffer.alloc(32,key,"binary");

let cipher = crypto.createCipheriv('aes-256-cbc', password_hash, iv);

let encryptedData = cipher.update(data, 'utf8', 'base64') + cipher.final('base64');

console.log('Base64 Encrypted:', encryptedData);

let decipher = crypto.createDecipheriv('aes-256-cbc', password_hash, iv);

let decryptedText = decipher.update(encryptedData, 'base64', 'utf8') + decipher.final('utf8');

console.log('Decrypted Text:', decryptedText)

function hex2bin(hex)
{
  var bytes = [], str;

  for(var i=0; i< hex.length-1; i+=2)
    bytes.push(parseInt(hex.substr(i, 2), 16));

  return String.fromCharCode.apply(String, bytes);
}

Try this Javascript code here.

You can try the Javscript and PHP examples at repl.it:

https://repl.it/@MarcusTettmar/phpaes256cbc1
https://repl.it/@MarcusTettmar/jsaes267cbc1

May 15, 2018

Nice Customer Testimonial

Filed under: General — Marcus Tettmar @ 12:20 pm

Just had this testimonial from a recent customer and had to share it with you:

“Just wanted to take some time to say thank you for helping us out on this project at such short notice. We’d sunk quite some time into getting up to speed with a competing product, and had bought personal licenses as well as evaluation and then to find that it could not script all the parts we needed, and frankly how terrible their support was left us in a very bad spot – thank God we’d not stumped up the £3k for their .NET module which we were on the verge of doing. We quickly ran through 4 other web automation packages – none of which were up to the job for our task, or the learning curve was too steep to find out – then stumbled on your Macro Scheduler. If only we’d got to you first. Thanks again, I have no doubt we’ll find use for Macro Scheduler on other projects – it’s a very useful tool to have in the tool-box now.”

You can read more testimonials here.

April 19, 2018

Macro Scheduler Subscriptions from only 12.50/month

Filed under: Announcements,General — Marcus Tettmar @ 8:40 am

Did you know we offer monthly, quarterly or yearly subscriptions for Macro Scheduler Standard?

These subscriptions have no minimum term, so can work out very cost effective if you only need the product for a short while. You may also prefer the budgeting aspect of fixed priced subscriptions over the up front cost of a perpetual license and then potential future upgrade or maintenance costs.  I know I do.

A subscription gives you access to the latest version of Macro Scheduler for as long as your subscription is active. So maintenance and upgrades are included and you never go out of date.

Check our the subscription options here.

 

April 5, 2018

Macro Scheduler 14.4.08 Available

Filed under: Announcements — Marcus Tettmar @ 4:42 pm

Macro Scheduler maintenance build 14.4.08 is now available for download from the usual places:

Registered Downloads/Upgrades | Trial Versions | New Licenses | Version History

This version introduces a simple and useful new WebRecorder function called IEGetAllText. It simply gets ALL the text in the specified Internet Explorer browser window, and it will iterate through all frames in the page too. A super simple way of scraping a web page or web app. You can tell it to get just text or html. Once you have that you could parse it with e.g. Regular Expressions.

While we already have functions that let you target a specific element based on a known attribute, this function gives you a quick way of pulling out all the text in the document.

April 4, 2018

Running Powershell Code From Inside Your Macros

Filed under: Automation,Scripting — Marcus Tettmar @ 4:25 pm

A while back I wrote this blog post which shows you how you can have a PowerShell script run Macro Scheduler commands via the MacroScript SDK.

However, what if you want to do it the other way around and run PowerShell code from inside a Macro Scheduler macro instead?

One way would simply be to run a PowerShell script on the command line, like this:

Run>powershell.exe -file "c:\myfolder\myscript.ps1"

But what if you didn’t have a PowerShell script file?  Well, while you could always have your macro create one using the WriteLn command (and delete it after), you don’t need to as you can pass a sequence of commands to PowerShell on the command line:

powershell.exe -executionPolicy bypass -command "Write-Host 'Hello World'; exit 123;"

As you can see all we’re really doing is passing the content of the script instead of a file.

We can also return the exit code of the PowerShell script to a variable in your macro.  Here’s an example:

//This line ensures the powershell script finishes before the macro continues
Let>RP_WAIT=1
//Uncomment this line to HIDE the powershell window when ready to go live
//Let>RP_WINDOWMODE=0

Let>localVar=Hello World
LabelToVar>MyPowershellScript,theScript
Run>powershell.exe -executionPolicy bypass -command "%theScript%"

//display the exit code
MDL>RP_RESULT

/*
MyPowershellScript:

$var = '%localVar%';
Write-Host $var;
Start-Sleep -s 10;
exit 555;
*/

What we’ve done here is store our PowerShell code in a label. We get that code into a variable and then pass it to PowerShell on the command line.

Notice how we can embed Macro Scheduler variables in the code too. We use PowerShell’s exit statement to set a return value which Macro Scheduler gets in the RP_RESULT variable.

Setting RP_WAIT to 1 makes sure the macro waits for PowerShell to finish before proceeding with the rest of the script. You may also want to hide the PowerShell window by setting RP_WINDOWMODE to 0, but for testing purposes I’ve left that commented out so that you can actually see it working. That’s also the reason for the 10 second delay at the end of the PowerShell script – apart from showing how we can add multiple lines of code it also gives us a chance to see things happen!

Get Date of Last Monday

Filed under: Scripting — Marcus Tettmar @ 2:59 pm

Someone asked me the other day how to get the date of the most recent Monday.  I came up with two solutions.

Get Date of Last Monday with VBScript

This version works by looping the date backwards one day at a time until the weekday of that date is a Monday.

//Put this VBSTART ... VBEND block at the top of your code ONCE
VBSTART
  Function LastMonday
    LastMonday = Date()
    Do While WeekDay(LastMonday) <> 2
      LastMonday = LastMonday -1
    Loop
  End Function
VBEND

//use this line whenever you want to get the date of last monday
VBEval>LastMonday,dateOfLastMonday

To make it work for a different day just change the number it compares the WeekDay to in the Do line. 1 is Sunday, 2 is Monday and so on.

Get Date of Last Monday with MacroScript

This version works out how many days we need to deduct from the current date to get to Monday based on what the current day of the week is. If it’s a Sunday we just deduct 6. If it’s a Monday or onward we deduct the current day number.

//DayOfWeek starts with 1 as Sunday, so Monday is 2.
If>curDayOfWeek>1
  //today must be Monday or later ... 
  //So diff we want is 2 - current day of week
  Let>diffToMonday=2-curDayOfWeek
  //e.g. if today Monday result would be 0 difference, Tuesday would yield -1 difference, Wednesday, -3 etc
Else
  //curDayOfWeek is 1, so ...
  //today must be sunday, so we just want 6 days ago
  Let>diffToMonday=-6
Endif

//This should give us number of days to subtract from current day 
//to get Monday's date
DateAdd>today,D,%diffToMonday%,mbeg

April 2, 2018

Escaping File Paths in HTTP Requests

Filed under: Automation,Web/Tech — Marcus Tettmar @ 5:48 pm

If you ever need to send a path as a URL parameter in an HTTP request, be sure to “escape” it to avoid the path delimiters (“\” symbols) being seen as delimiters in the URL path.

E.g. let’s say you need to send a filename as a parameter in a GET request, like this:

https://someserver.com/resource?filename=c:\my files\subfolder\filename.txt

If we fail to escape those “\” characters we are likely to get a server error or a 404 not found error at best. The space character in the above example is also likely to upset things!

We can solve this by using VBScript’s Escape function to “URL Encode” the string. What this does is replace the special characters with a special code made of a % symbol and two hexadecimal digits. E.g. a space character is replaced with %20.

So, we should use the Escape function something like this:

Let>strFilename=c:\my files\subfolder\filename.txt
VBEval>Escape("%strFilename%"),strFilename

Then we can safely do:

HTTPRequest>https://someserver.com/resource?filename=%strFilename%,,GET,,strResponse

If you were to paste the URL into Google Chrome or IE you will find that behind the scenes they apply this encoding for you.

Tip: A great way to test HTTP requests is to use http://webhook.site – this gives you a special URL you can send your data to (in place of your real web service) and for each request you make it will show you what it received. Try manually sending a filename like the one above using your web browser and you’ll see those % codes being added for you.

March 28, 2018

Will robotic process automation take your job away?

Filed under: Automation,General,Macro Recorder — Marcus Tettmar @ 2:25 pm

TLDR: No! That’s not what we’re here for. Robotic process automation is here to help you.

We often get called in to help a company automate a process. Many times this involves automating something that an end-user does. To make macros that make their job easier, less painful, and make them more productive.

Initial Resistance to Change

Sometimes, at the outset, we (or the IT department proposing Macro Scheduler as a solution) detect a little resistance from the end user. If not properly briefed they may be concerned that what we want to do is take work away from them, and they may be concerned that the macros, or software robots, are about to take their jobs away.

Epiphany!

Nothing could be further from the truth and by the time we’re done, the end users love us to bits and usually give us a long list of other things they’d like us to automate! Often they discover how easy it is to do it themselves with tools like the Macro Recorder and other code wizards.

I’ve written thousands of macros for hundreds of companies. I’ve never worked on anything that was designed to take a job away from an employee, other than one the employee didn’t like doing and didn’t have time to do without it impacting on their core purpose. In every case the macros we wrote were there to make the employees’ jobs easier, or to allow them to be more productive in more important areas. For example: removing a tedious admin task from an already overstretched clinician, allowing them to see more patients.

I suppose you could argue that the many over-night data-entry style macros we’ve written could have been done instead by a team of data entry personnel. But such a team didn’t exist to start with, or would have been cost prohibitive, or too error-prone to be considered in the first place.

Improving Productivity

So to answer the question, no, I don’t believe robotic process automation is about taking jobs away. It’s not about replacing staff. In my 21 years of automating user interfaces this isn’t what I’ve seen. Instead, I’ve been rewarded with making people’s lives easier, removing cumbersome, painful, processes, or using macros to simplify and improve an existing process.

« Newer PostsOlder Posts »