Module 36: VNC Scripting

Introduction

VNC recordings are just plain text files with commands.

Format

Files can contain: mouse events, keyboard events, load events, and no actionable lines. Anything that does not have proper syntax or has invalid key definitions will be ignored.

Mouse Events

574598:PointerEvent,0,100,669

574598 – Is the time to wait in nanoseconds before doing an action.

Note: Adding 6 decimal places to microseconds will get you nanoseconds. A, 1 and 9 zeros is a second. In this case 574598 is ~0.575ms.

PointerEvent – Issues either a mouse click or a click release

0 – Is a non clicked state. 1 is a clicked state.

100,669 – Are the x and y mouse coordinates. Where 0,0 is the top left.

When writing mouse events by hand you should surround them with non-click presses as well; that way, the OS can move the mouse to where it needs to be and you can stop clicking.

50000000:PointerEvent,0,100,669 50000000:PointerEvent,1,100,669 50000000:PointerEvent,0,100,669

Keyboard Events

1000000:KeyEvent,true,a 1000000:KeyEvent,false,a 1000000 - Is the time to wait in nanoseconds before doing an action. (1ms)

KeyEvent – Issues a either a keydown or keyup event.

true – Is a key down event, false is a key up event.

Special Keyboard Keys

Every single key is defined in a map in the source code link

Here are most of the ones you will use. Note that these keys are case-sensitive.

space BackSpace Tab Return Super_L Shift_L Control_L Alt_L Escape Delete Left Up Right Down Home End Insert slash backslash underscore bracketleft bracketright braceleft braceright question exclam at numbersign dollar percent ampersand parenleft parenright asterisk apostrophe quotedbl colon semicolon comma period minus grave less equal greater bar 1 a F1

If a key requires a shift on your keyboard you will likely need to do the same regardless of the key’s name. For example, colon:

50000:KeyEvent,true,Shift_L 50000:KeyEvent,true,colon 50000:KeyEvent,false,colon 50000:KeyEvent,false,Shift_L

LoadFile

The playback file supports a LoadFile event. LoadFile will take an existing VNC keyboard/mouse recording and, if a playback is currently running, preempt the running playback and play the specified playback file to completion. After the LoadFile playback completes, the previously running playback will resume and continue playing. If LoadFile is injected with no playback currently running it will start a new VNC playback with the specified playback file.

10000000000:LoadFile,/home/john/recordings/reboot_windows.vnc

You can inject LoadFile events in the same way as other keyboard or mouse events:

# inject the LoadFile event to vm foo to play the playback bar.kbr vnc inject foo LoadFile,bar.kbr  

WaitForIt

Another special event is the WaitForIt event. This event causes the playback to search the VM’s screenshot for the template image and continue once the template image has been found. The event supports a timeout which will cause the playback to stop if exceeded.

1000:WaitForIt,10s,template.png

You may also base64-encode the image and include it in place of the filename. This allows your VNC scripts to be self-contained.

ClickIt

Similar to the WaitForIt event, the ClickIt event waits until a template image appears in the VM screenshot but has an additional action to click on the center of the template image in the screenshot.

1000:ClickIt,10s,template.png

As with the WaitForIt event, you may base64-encode the image to use in place of the filename.

Tools

Creating these files by hand may be exhausting. Here are some scripts to help out.

Type a string

# genKeys.py import sys def vncType(t):  retstr = ""  shift = 0  for c in t:   shift = 0   if c == ".":    c = "period"   if c == " ":    c = "space"   if c == "/":    c = "slash"   if c == "\\":    c = "backslash"   if c == ":":    c = "colon"    shift = 1   if c == "@":    c = "at"    shift = 1   if c == "!":    c = "exclam"    shift = 1   if c == "#":    c = "numbersign"    shift = 1   if c == "$":    c = "dollar"    shift = 1   if c == "%":    c = "percent"    shift = 1   if c == "&":    c = "ampersand"    shift = 1   if c == "\'":    c = "apostrophe"   if c == "(":    c = "parenleft"    shift = 1   if c == ")":    c = "parenright"    shift = 1   if c == "*":    c = "asterisk"    shift = 1   if c == "+":    c = "plus"    shift = 1   if c == ",":    c = "comma"   if c == "-":    c = "minus"   if c == ";":    c = "semicolon"   if c == "<":    c = "less"    shift = 1   if c == ">":    c = "greater"    shift = 1   if c == "?":    c = "question"    shift = 1   if c == "=":    c = "equal"   if c == "[":    c = "bracketleft"   if c == "]":    c = "bracketright"   if c == "{":    shift = 1    c = "braceleft"   if c == "}":    shift = 1    c = "braceright"   if c == "_":    c = "underscore"    shift = 1   if c == "\"":    c = "quotedbl"    shift = 1   if c == "\n":    c = "Return"   if c == "\t":    c = "Tab"   if shift == 1:    retstr = retstr +"50000000:KeyEvent,true,Shift_L\n"   retstr = retstr + "50000000:KeyEvent,true,"+c+"\n50000000:KeyEvent,false,"+c+"\n"   if shift == 1:    retstr = retstr +"50000000:KeyEvent,false,Shift_L\n"  return retstr.rstrip("\n") print vncType(sys.argv[1])
# python genKeys.py "P@ss w0rd" 50000000:KeyEvent,true,P 50000000:KeyEvent,false,P 50000000:KeyEvent,true,Shift_L 50000000:KeyEvent,true,at 50000000:KeyEvent,false,at 50000000:KeyEvent,false,Shift_L 50000000:KeyEvent,true,s 50000000:KeyEvent,false,s 50000000:KeyEvent,true,s 50000000:KeyEvent,false,s 50000000:KeyEvent,true,space 50000000:KeyEvent,false,space 50000000:KeyEvent,true,w 50000000:KeyEvent,false,w 50000000:KeyEvent,true,0 50000000:KeyEvent,false,0 50000000:KeyEvent,true,r 50000000:KeyEvent,false,r 50000000:KeyEvent,true,d 50000000:KeyEvent,false,d

Type a file

Add this to the end of genKeys.py to type a whole file.

with open ("file.txt", "r") as myfile:     data=myfile.readlines() for c in data:     vncType(c)

Replacing mouse movements with merged waits in recordings

#!/usr/bin/python  import sys  if len(sys.argv) != 2:  print "Syntax Error: Expecting\n./shrink.py input_file"  exit(1)  global timer timer = 0 global changetimer changetimer = 1 with open (sys.argv[1], "r") as myfile:  for line in myfile:   if 'PointerEvent,0' in line:    split = line.split(":")    split = split[0]    timer = timer + int(split)   else:    if 'PointerEvent,1' in line or 'PointerEvent,4' in line:     if timer == 0:      print line.rstrip('\n')      split = line.split(",")      print ("10000:PointerEvent,0,"+split[-2]+","+split[-1]),     else:      split = line.split(",")      print (str(timer)+":PointerEvent,0,"+split[-2]+","+split[-1]),      line = line.lstrip('\n')      print line.rstrip('\n')      print ("10001:PointerEvent,0,"+split[-2]+","+split[-1]),    elif 'KeyEvent' in line:     if timer != 0:      print str(timer)+":PointerEvent,0,0,0"      print line.rstrip('\n')     else:      print line.rstrip('\n')    else:     # looks like a comment     print line.rstrip('\n')     changetimer = 0    if changetimer ==1:     timer = 0    else:     changetimer = 1  print str(timer)+":PointerEvent,0,0,0"

Printing coordinates as your mouse moves

nano minimega/misc/web/novnc/include/input.js ctrl+w and search for onMouseMove(pos.x Modify the code to have the nested if/else section added.
var evt = (e ? e : window.event); var pos = Util.getEventPosition(e, this._target, this._scale);     if (this._onMouseMove) {         if (noVNC_status.innerHTML.indexOf('|')==-1){             noVNC_status.innerHTML += " | X:"+pos.x+" Y:"+pos.y;         }         else{             noVNC_status.innerHTML=noVNC_status.innerHTML.split("|")[0]+"| X:"+pos.x+" Y:"+pos.y;         }         this._onMouseMove(pos.x, pos.y);     }

The result should look like this.

Image of posbanner.png

Checking if a smaller picture exists on the screen.

Useful for waiting for something before clicking on it.

#apt-get install python-opencv import cv2 smallimgpath="/home/ubuntu/small.png" largeimgpath="/home/ubuntu/large.png" small = cv2.imread(smallimgpath) large = cv2.imread(largeimgpath) method = cv2.TM_CCOEFF_NORMED res = cv2.matchTemplate(large,small,method) min_val,max_val,min_loc,max_loc=cv2.minMaxLoc(res) threshold=0.8 if(max_val < threshold):  print "not found" else:  small_h,small_w,small_k=small.shape  x_center =max_loc[0] +small_w/2  y_center =max_loc[1] +small_h/2  print "Center found: "+str(x_center)+","+str(y_center)

You can take a screenshot of a vm using vm screenshot

vm screenshot myvm file /home/ubuntu/test.png

Example VNC Script

# Click on the screen to disable screensaver 50000000:PointerEvent,0,100,750 50000000:PointerEvent,1,100,750 50000000:PointerEvent,0,100,750 # Wait 10s 10000000000:PointerEvent,0,0,0 # Press Windows+r 50000000:KeyEvent,true,Super_L 50000000:KeyEvent,true,r 50000000:KeyEvent,true,r 50000000:KeyEvent,false,Super_L # Wait 10s 10000000000:PointerEvent,0,0,0 # Type cmd.exe enter 50000000:KeyEvent,true,c 50000000:KeyEvent,true,c 50000000:KeyEvent,true,m 50000000:KeyEvent,true,m 50000000:KeyEvent,true,d 50000000:KeyEvent,true,d 50000000:KeyEvent,true,period 50000000:KeyEvent,true,period 50000000:KeyEvent,true,e 50000000:KeyEvent,true,e 50000000:KeyEvent,true,x 50000000:KeyEvent,true,x 50000000:KeyEvent,true,e 50000000:KeyEvent,true,e 50000000:KeyEvent,true,Return 50000000:KeyEvent,true,Return # Wait 10s 10000000000:PointerEvent,0,0,0 # Alt+F4 50000000:KeyEvent,true,Alt_L 50000000:KeyEvent,true,F4 50000000:KeyEvent,true,F4 50000000:KeyEvent,false,Alt_L

Authors

The minimega authors

Created: 14 Jun 2017

Last updated: 3 June 2022