Sunday, 28 August 2011

Reading the Clipboard from VBScript

I was recently working on some scripting for managing a couple of hundred Cisco devices, to automate bulk ACL changes, backups and suchlike via PuTTY. At one point I came to the conclusion that it would be useful to be able to access the clipboard from a vbscript running under cscript.exe, and went looking for some starter code. I was surprised to find that no such code existed, or that if it existed, it relied on an external program such as clip.exe.

The problem seems to be that clipboard lives in Gui userland, and my scripts live in Text userland. So the solution I would need to come up with would have to go to the Windows environment in order to access the abstraction that is clipboard, and bring it back to my text environment.

My first cut solution uses a pseudo-netsocket approach - spawn another process and establish two-way communication using PIDs, then paste into its windows interface, and have it send what it receives to my stdin.

It works surprisingly well (in my environment) and although ultimately I decided not to use it for my Cisco project, I have added it to my libaries for process management. Here it is with only minimal error checking for clarity.

Option Explicit

'Name:    GetClipBoard() function
'Author:  Philip Damian-Grint
'Version: 1.0
'Date:    28th Aug 2011
'  From a vbs script running under cscript.exe, read the contents of the 
'  Clipboard into a string.

Function GetClipBoard

    ' First we create a text file to hold our child
    dim objFS : Set objFS = CreateObject("Scripting.FileSystemObject")
    dim strFName : strFName = objFS.GetTempName
    dim objTS : Set objTS = objFS.CreateTextFile( strFName, True )

    ' Our child requests her parent's PID, and then provides a paste buffer, all off-screen
    objTS.WriteLine("dim pid : pid=inputbox(""PID"",,,0,-3000) : " & _
            "dim str : str=inputbox(""STR"",,,0,-3000) : " & _
            "set shell=wscript.createobject("""") : " & _
            "shell.appactivate pid : wscript.sleep 100 : " & _
            "shell.sendkeys str & ""{ENTER}""")

    ' Spawn our child as a running process
    Dim objWshShell : Set objWshShell = WScript.CreateObject("WScript.Shell")
    Dim objChild : Set objChild = objWshShell.exec( "cscript.exe //E:vbscript " & strFName )
    Dim intChildPID : intChildPID = clng(objChild.ProcessID)

    ' Now use our child's PID to find our own
    Dim strObjPath : strObjPath = "winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2"
    Dim objProcess, intParentPID

    For Each objProcess In getObject( strObjPath ).instancesOf("Win32_Process")
        If intChildPID = (clng(objProcess.processID)) Then
            intParentPID= objProcess.parentProcessID : Exit For
        End If

    ' Find our child's first input box, and write our PID to it
    Do until objWshShell.AppActivate( intChildPID )
        WScript.Sleep 100
    Loop : objWshShell.SendKeys intParentPID & "{ENTER}"

    ' Find our child's second input box, and paste the clipboard contents
    Do Until objWshShell.AppActivate( intChildPID )
        WScript.Sleep 100
    Loop : objWshShell.SendKeys "^v{ENTER}"

    ' Receive the paste buffer contents from our child
    GetClipBoard  = WScript.StdIn.ReadLine

    ' And clear up after our child
    objFS.DeleteFile strFName, True

End Function

' Demonstrate the function

wscript.echo "We read: " & GetClipBoard() & " from the clipboard"

1 comment:

  1. This comment has been removed by a blog administrator.