1

I sometimes use the MSForms.DataObject object from the Microsoft Forms 2.0 Object Library in Excel VBA. It is absolutely wonderful for reading / writing text data from / to the clipboard. Recently, I stumbled across this article which shows how to instantiate the object using late binding and found that it works beautifully in VBA. Now, I don't have to worry about adding the reference library each time I port my code to new projects.

That discovery made me wonder if it were possible to use the same object in VBScript. There have been several instances in the past when I wanted to manipulate the clipboard with VBScript but all my research at the time indicated that it wasn't possible (aside from using internet explorer, mshta, clip, etc). To my surprise, the DataObject worked exactly as expected when I tried to read the clibboard. However, it would not put data back into the clipboard and threw an error which makes no sense to me. Below are the details.

  • Error Number: -2147221008 (800401F0)
  • Error Description: DataObject:PutInClipboard CoInitialize has not been called.

So, is there a workaround for the error I'm getting or is it simply part of the same VBScript limitation described on MSDN and this answer?

Here is the VBScript code I used for testing on my 64 bit Windows 7 PC:

Option Explicit
Dim DObj

Sub TestClipboard()
    Dim ClipData
    VerifyArchitecture
    If Not InitClipboardObject Then
        Terminate "Unable to initialize the clipboard object"
    ElseIf Not ClipboardPaste(ClipData) Then
        Terminate "Unable to retrieve the clipboard data"
    End If

    ' The message box will have the current clipboard text (if any exist)
    MsgBox "The clipboard contains the following text:" & _
    vbCrLf & vbCrLf & ClipData
    ClipData = "Text we put in the clipboard"
    ' However, this function will not succeed.
    If Not ClipboardCopy(ClipData) Then Terminate "Unable to put data into the clipboard"
End Sub

Function InitClipboardObject()
    On Error Resume Next
    ' If the code is run in VBA, the following reference library
    ' can be used as an alternative to late binding:
    '   Microsoft Forms 2.0 Object Library
    '       Note:  The reference library will not show up on the
    '       list unless a userform has already been added in Excel.
    '       If not, browse for the FM20.DLL file
    Set DObj = GetObject("new:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
    InitClipboardObject = Err = 0
End Function

' Put data in the clipboard similar to pressing Ctrl + C
Function ClipboardCopy(ByVal Data)
    On Error Resume Next
    DObj.SetText Data
    ' This line of code will throw the following error
    '   Error Number: -2147221008 (800401F0)
    '   Description: DataObject:PutInClipboard CoInitialize has not been called.
    ' However, it works perfectly in VBA
    DObj.PutInClipboard
    ClipboardCopy = Err = 0
End Function

' Retrieve data from the clipboard similar to pressing Ctrl + V
Function ClipboardPaste(ByRef Data)
    On Error Resume Next
    DObj.GetFromClipboard
    Data = DObj.GetText(1)
    ClipboardPaste = Err = 0
End Function

' This sub will re-load the script using the 32 bit host
' if it is loaded on the 64 bit version.  This is necessary
' since the clipboard object is 32 bit.
Sub VerifyArchitecture()
    ' The code in this sub is a modified version of Vozzie's answer
    ' and I do not take credit for the idea:
    '   https://stackoverflow.com/a/15320768/2734431
    Dim Arch, Arg, Args, Cmd, ExeFullName, ExeShortName
    Dim Path32, Path64, ProcEnv, q, Reload, ScriptName
    Dim WinDir, WShell
    q = Chr(34)
    Reload = False
    ExeFullName = WScript.FullName
    ScriptName = WScript.ScriptFullName
    ExeShortName = Mid(ExeFullName, InStrRev(ExeFullName, "\") + 1)
    Set WShell = CreateObject("WScript.Shell")
    Set ProcEnv = WShell.Environment("Process")
    WinDir = ProcEnv("windir") & "\"
    Path32 = WinDir & "SysWOW64\"
    Path64 = WinDir & "System32\"
    Arch = ProcEnv("PROCESSOR_ARCHITECTURE")

    For Each Arg In WScript.Arguments
        Args = " " & q & Arg & q
    Next
    Cmd = q & Path32 & ExeShortName & q & " " & q & ScriptName & q & Args
    If InStr(LCase(ExeFullName), LCase(Path64)) <> 0 And Arch = "AMD64" Then
        Reload = True
        WShell.Run Cmd
    End If

    Set WShell = Nothing
    Set ProcEnv = Nothing
    If Reload Then Terminate ""
End Sub

' This sub is designed to clear any global variables, optionally
' display an error message, and stop the script
Sub Terminate(ByVal ErrMsg)
    Dim ErrNbr
    Set DObj = Nothing
    If ErrMsg <> "" Then
        ErrNbr = "Error"
        If Err <> 0 Then
            ErrNbr = ErrNbr & " " & Err & " (" & Hex(Err) & ")"
            ErrMsg = ErrMsg & vbCrLf & vbCrLf
            ErrMsg = ErrMsg & "Code Error: " & Err.Description
        End If
        ' &H10 = vbCritical
        MsgBox ErrMsg, &H10, ErrNbr
    End If
    WScript.Quit
End Sub
ashleedawg
  • 20,365
  • 9
  • 72
  • 105
Chris D
  • 351
  • 1
  • 13
  • It looks like something else happens behind the scenes in VBA which properly initializes the COM reference counter. I haven't figured out a way to make it work in VBS (I should probably update the artice to mention that!) but will post back if I do. – Rory Oct 03 '17 at 21:29
  • @Rory I think I found the reason why the code won't work fully in VBScript. Based on [this](http://www.georeference.org/Forum/t64217.9) post, it seems that the issue has something to do with the subsystem type (not sure what that means) used when WSH is compiled and the fact that it uses `CoInitialize` instead of `OleInitialize`. From what I read on [MSDN](https://msdn.microsoft.com/en-us/library/windows/desktop/ms690134(v=vs.85).aspx) `OleInitialize` is required to enable clipboard functionality. It seems as though VBA handles that natively. – Chris D Oct 20 '17 at 16:25
  • Thanks for letting me know. I'll update the blog when I have a moment. – Rory Oct 20 '17 at 16:43

0 Answers0