Accessing WebDAV in Microsoft Word Visual Basic

Introduction

In “[intlink id="64" type="post" target="_self"]Document Management System in Word using WebDAV[/intlink]” I explained why I needed to connect Microsoft Word to a WebDAV repository and in “[intlink id="43" type="post" target="_self"]PHP based authentication for mod_dav[/intlink]” I explained how to set up authorisation for mod_dav using a PHP script. What’s left is accessing the WebDAV repository using Word’s Visual Basic.

Requirements

  • Open documents from the remote location.
  • Save documents to the remote location.
    • Save new documents to the remote location.
    • Overwrite existing documents on the remote location.
  • List documents available for specified MemberID’s.
  • Add meta-data to stored documents. (Optional)

Solution

Starting from Office 2000, support for WebDAV is included natively. Although Windows XP and never also have their own support for WebDAV, it’s always better to use Office’s own methods. The only downside to this is that these methods are not available in Word’s Visual Basic. ((Office’s own WebDAV support might be available in VBA, but I have not found any documentation on it.))

Word Visual Basic WebDAV interface

Accessing WebDAV resources in Word’s Visual Basic can be done by using an ADODB object. Starting from ADODB 2.5 support for WebDAV is available. ADODB might need to be enabled for Word first.

Activate ADODB in Word

Visual Basic Editor (Word 2003)

Visual Basic Editor (Word 2003)

To activate (or check if it is available) ADODB in Word, you need to start the Visual Basic Editor. The VBE can be found in Tools\Macro (Word XP & 2003 ((Office 2007 might have hidden it better but ALT+F11 still works.)))

Access to ADODB is determined by the project’s references and those are specific for each Word document, so make sure to start your WebDAV macro projekt first (Done by creating a new Macro from Word).

VBE Rreferences (Word 2003)

VBE Rreferences (Word 2003)

Open the References from Tools\References and make sure a “Microsoft ActiveX Data Objects 2.x Library” is selected. Versions 2.5 and newer support WebDAV, but make sure the production environment you will be delivering to has the same version.

As soon as the ADODB library is activated the Visual Basic Editor will recognise the ADODB references in the code.

Opening a document from WebDAV

To open a document from WebDAV the ADODB object is not needed, Word uses it’s internal WebDAV library instead.

Documents.Open "http://example.org/dav/document.doc", False, False

Sometime the opened document becomes read-only, this is can have several causes:

  • WebDAV supports locks and the file has been locked. Just like normal local access Word puts a lock on the file if opened for writing. Unlike local access the lock on WebDAV can easily be removed by an administrator (or another Word Macro! See “[intlink id="43" type="post" target="_self"]PHP based authentication for mod_dav[/intlink]” to know how even this can be moderated with a PHP script.)
  • Word erronously identified the WebDAV as an Exhange or SharePoint resource. Both Microsoft Exchange and Microsoft SharePoint use WebDAV to share documents with Office. The problem is caused by the extra meta-data that is expected.

Save a new document to WebDAV

Documents opened from a WebDAV resource are automatically saved back to their original WebDAV location, unless they are opened as read-only. To save a newly created document to a WebDAV repository, just pass the URL to the normal SaveAs function.

ActiveDocument.SaveAs ("http://example.org/dav/test.doc")

Getting a directory listing from WebDAV

Other actions that interact with the WebDAV repository require the ADODB object. Using ADODB creates an interface for WebDAV that acts like a database, with recordsets and records.

Listing 1:

'
' Microsoft Office Visual Basic
' WebDAV Access example
'
' @author Jeffrey Ridout
' @date 2009-04-23
' @version 0.0.1
'

Sub AccessWebDAV()
    Dim files() As String                  ' String array to hold friendly filenames.
    Dim davDir As New ADODB.Record         ' Record for the directory
    Dim davFile As New ADODB.Record        ' Record for the file
    Dim davFiles As New ADODB.Recordset    ' Recordset for the directory's filelist
    Dim isDir As Boolean
    Dim index As Integer

    ' For debugging purposes jump to the error display on any error
    On Error GoTo showErr

    ' Open the root folder as a ADODB.Record to retrieve a list of it's contents.
    ' To open a folder, pass an empty string as the filename and the full URL as the path.
    ' URL= tells ADODB to use the WebDAV interface.
    ' adModeReadWrite: This could be adModeReadOnly, it might matter in a multi-threaded environment
    ' adDelayFetchStream: ADODB sometimes pre-fetches the content of files, this is not needed.
    davDir.Open "", _
                "URL=http://example.org/dav", _
                adModeReadWrite, _
                adFailIfNotExists, _
                adDelayFetchStream, _
                "username", _
                "password"
    Set davFiles = davDir.GetChildren()          ' Get the list of files as a ADODB.Recordset

    index = 0
    ' The WebDAV directory Recordset uses a 'current' index and has no counter,
    ' so use EOF to check if there are more files.
    Do While Not davFiles.EOF
        davFile.Open davFiles, , adModeReadWrite
        ' File properties are Record.Fields, all standard DAV fields are called "Dav:<property>".
        ' ADODB adds it's own fields that supply the same information, called "RESOURCE_<property>".
        isDir = davFile.Fields("RESOURCE_ISCOLLECTION").Value                     ' RESOURCE_ISCOLLECTION is a boolean specifiying if the file is a directory.

        If Not isDir Then ' If the file is not a directory it's a normal file and we can add it to the list.
            ReDim Preserve files(0 To 1, 0 To index)                              ' The number of files is unknown, so "ReDim Preserve" resizes the array without losing the data.
            files(0, index) = davFile.Fields("RESOURCE_PARSENAME").Value          ' RESOURCE_PARSENAME contains the filename.
            files(1, index) = davFile.Fields("RESOURCE_ABSOLUTEPARSENAME").Value  ' RESOURCE_ABSOLUTEPARSENAME contains the full URL.
            index = index + 1
        End If

        davFile.Close       ' Always remember to Close the file!
        davFiles.MoveNext   ' Move the index to the next Record.
    Loop
    Set davFiles = Nothing  ' Always unset the object pointer!
    davDir.Close            ' Close the Record for the directory, not closing it can leak memory and could also lock the directory.
    Set davDir = Nothing

    ' Do something with files here...

    GoTo noErr
showErr:
    Call MsgBox(Err.Number & ": " & Err.Description, vbOKOnly, "Error")

noErr:
    ' No errors
End Sub

Getting and setting meta-data

Exchange and SharePoint use custom properties to perform extra actions and change file attributes. Custom properties in WebDAV normally have their own namespace, but I haven’t figured out how to specify those with ADODB yet. ((If you find out, let me know!)) Some examples contain the namespace as part of the property, so I’ve adopted this.

Adding properties for the document’s title, subject, author, etc. makes it possible to display those properties without having to open the document. Whenever the document is saved to WebDAV a Macro function should be triggered to update these properties.

Listing 2:

'...
' Define some constants containing property namespaces in the head of the Macro.

' Constants
Public Const OFFICE_URN  As String = "urn:schemas-microsoft-com:office:office"
Public Const EXAMPLE_URN As String = "http://example.org/dav/props/"

' Inside the function, the custom properties can be retrieved and added quite easy.
'...
    ' Get custom properties.
    prop1 = davFile.Fields(OFFICE_URN & "author").Value
    prop2 = davFile.Fields(EXAMPLE_URN & "doc_title").Value
    prop3 = davFile.Fields(EXAMPLE_URN & "doc_subject").Value
    prop4 = davFile.Fields("non-existing-property").Value      ' Non existing properties return an empty string.

    ' Set custom properties.
    davFile.Fields(OFFICE_URN & "author") = "user"
    davFile.Fields(EXAMPLE_URN & "doc_title") = "Document title"
    davFile.Fields(EXAMPLE_URN & "doc_subject") = "Document subject"
    davFile.Fields.Update  ' Fields.Update commits the changes to Fields to WebDAV.
'...

Putting it all together

Here is the code where all previous techniques are put together. Normally opening, saving and listing would be done in different functions preferably triggered by events, put this will serve as an example.

Listing 3:

'
' Microsoft Office Visual Basic
' WebDAV Access example
'
' @author Jeffrey Ridout
' @date 2009-04-23
' @version 0.0.2
'

' Constants
Public Const OFFICE_URN As String = "urn:schemas-microsoft-com:office:office/"
Public Const SPK_URN As String = "http://schemas.spk.no/dav/props/"

' Module variables
Public isCancelled As Boolean  ' Used by dlgOpenFileDAV
Public fileURL As String       ' Used by dlgOpenFileDAV

Sub OpenWebDAVFile()
    Dim files() As String                  ' Dynamic Multidimensional String array to hold file properties.
    Dim davDir As New ADODB.Record         ' Record for the directory
    Dim davFile As New ADODB.Record        ' Record for the file
    Dim davFiles As New ADODB.Recordset    ' Recordset for the directory's filelist
    Dim isDir As Boolean
    Dim index As Integer
    Dim dlg As Object

    ' For debugging purposes jump to the error display on any error
    On Error GoTo showErr

    ' Open the root folder as a ADODB.Record to retrieve a list of it's contents.
    ' To open a folder, pass an empty string as the filename and the full URL as the path.
    ' URL= tells ADODB to use the WebDAV interface.
    ' adModeReadWrite: This could be adModeReadOnly, it might matter in a multi-threaded environment
    ' adDelayFetchStream: ADODB sometimes pre-fetches the content of files, this is not needed.
    davDir.Open "", _
                "URL=http://example.org/dav", _
                adModeReadWrite, _
                adFailIfNotExists, _
                adDelayFetchStream, _
                "username", _
                "password"
    Set davFiles = davDir.GetChildren()          ' Get the list of files as a ADODB.Recordset

    index = 0
    ' The WebDAV directory Recordset uses a 'current' index and has no counter,
    ' so use EOF to check if there are more files.
    Do While Not davFiles.EOF
        davFile.Open davFiles, , adModeReadWrite
        ' File properties are Record.Fields, all standard DAV fields are called "Dav:<property>".
        ' ADODB adds it's own fields that supply the same information, called "RESOURCE_<property>".
        isDir = davFile.Fields("RESOURCE_ISCOLLECTION").Value                     ' RESOURCE_ISCOLLECTION is a boolean specifiying if the file is a directory.

        If Not isDir Then ' If the file is not a directory it's a normal file and we can add it to the list.
            ReDim Preserve files(0 To 4, 0 To index)                              ' The number of files is unknown, so "ReDim Preserve" resizes the array without losing the data.
            files(0, index) = davFile.Fields("RESOURCE_PARSENAME").Value          ' RESOURCE_PARSENAME contains the filename.
            files(1, index) = davFile.Fields("RESOURCE_ABSOLUTEPARSENAME").Value  ' RESOURCE_ABSOLUTEPARSENAME contains the full URL.
            files(2, index) = davFile.Fields(SPK_URN & "doc-title").Value         ' Custom property: title
            files(3, index) = davFile.Fields(SPK_URN & "doc-subject").Value       ' Custom property: subject
            files(4, index) = davFile.Fields(OFFICE_URN & "author").Value         ' Custom property: author
            index = index + 1
        End If
        davFile.Close       ' Always remember to Close the file!
        davFiles.MoveNext   ' Move the index to the next Record.
    Loop
    Set davFiles = Nothing  ' Always unset the object pointer!
    davDir.Close            ' Close the Record for the directory, not closing it can leak memory and could also lock the directory.
    Set davDir = Nothing

    ' See Image 3 and Listing 4 for the OpenFileDAV dialog.
    dlgOpenFileDAV.listDocuments.Clear     ' Clear the ListBox since dlgOpenFileDAV is reusable.
    For I = 0 To index - 1
        With dlgOpenFileDAV.listDocuments
            .AddItem files(0, I), I        ' Add a new row to the ListBox.
            .List(I, 1) = files(1, I)      ' The ListBox has 4 columns, 2nd column is hidden and contains the full URL.
            .List(I, 2) = files(2, I)      ' 3d column contains the title.
            .List(I, 3) = files(3, I)      ' 4th column contains the subject.
            .List(I, 4) = files(4, I)      ' 5th column contains the author.
        End With
    Next I

    isCancelled = True
    fileURL = ""
    dlgOpenFileDAV.Show
    If (isCancelled = False) Then
        If (fileURL <> "") Then
            Documents.Open fileURL, False, False  ' To open the file from the WebDAV repository, simple pass the URL.
        End If
    End If

    GoTo noErr
showErr:
    Call MsgBox(Err.Number & ": " & Err.Description, vbOKOnly, "Error")

noErr:
    ' No errors
End Sub

Image 3 shows the UserForm used as the custom “Open File” dialog. Image 4 shows the ListBox properties. The ListBox currently only should support selecting 1 file, has 5 columns. Note that the 2nd column has a width of “0 pt” in order to hide it. BoundColunm is set to 2, which results in the Value of the ListBox being the value of the hidden column of the selected row (The file’s full URL).

Image 2: Open WebDAV File dialog

Image 2: Open WebDAV File dialog

Image 5: ListBox properties

Image 5: ListBox properties

Listing 4:

Private Sub btnCancel_Click()
    davTest.isCancelled = True
    Hide
End Sub

Private Sub btnOk_Click()
    davTest.isCancelled = False
    davTest.fileURL = listDocuments.Value
    Hide
End Sub

Private Sub listDocuments_Change()
    If listDocuments.Value >= 0 Then
        btnOk.Enabled = True
    End If
End Sub

Private Sub UserForm_Initialize()
    If (listDocuments.ListCount > 0) Then
        listDocuments.Clear
    End If
    btnOk.Enabled = False
    davTest.isCancelled = True
End Sub

Conclusion

Creating your own WebDAV based repository access in Microsoft Word is possible and after some research and experimenting quite easy. Combine this with a PHP CMS and mod_dav with PHP authentication and authorisation and you’ve got a fully functional Document Management System.

Example

Creative Commons: Attribution Share AlikeAll code in this post is licensed under Creative Commons Attribution Share Alike.

14 thoughts on “Accessing WebDAV in Microsoft Word Visual Basic

  1. Pingback: list box column shown in password style | LabVIEW Resources

  2. This is excellent. I have two questions:

    1. How would one create a new directory on the WebDAV server using this interface?
    2. How would one copy a file without using Documents.Open and ActiveDocument.SaveAs?

    What I’m trying to do is create code that will copy a WebDAV folder and all of its files to another WebDAV folder. Using your code above, I can locate the folder and its contents, but I’m lost as to how to create a new folder and copy the contents of the newly created folder.

  3. @Steve
    I had to do a bit of testing and it might seem a bit odd, but here’s how to create directories:

    1. Open the parent directory as done in Listing 1.
    2. Grab the RecordSet for the children, as in Listing 1, line 30.

    Then create a new Record:

    
    If (davFiles.Supports(adAddNew)) Then
        Dim newDirFields(1) As Variant
        Dim newDirValues(1) As Variant
        newDirFields(0) = "RESOURCE_PARSENAME"
        newDirValues(0) = "newdirname"
        newDirFields(1) = "RESOURCE_ISCOLLECTION"
        newDirValues(1) = True
    
        davFiles.AddNew newDirFields, newDirValues
    End If
    

    Note that using AddNew moves the RecordSet to the end, thus EOF will be ‘True’.

  4. I was interested to see how your code worked and how I might be able to leverage it for a project I’m working on. I copied Listing 3 above and dropped it into a module then created the UserForm shown. However whenever I try to run the function it gives me that nasty little Object Expected error on the dlgOpenFileDAV.ListDocuments.Clear line. Thinking perhaps that the form wasn’t loaded I added that in above to no avail. Whenever I try to launch the UserForm it gives me the same error but on the line that reads davTest.isCancelled = True. I can’t seem to locate any definition for the davTest object. What am I missing?

  5. @Chad I’ve corrected a few typos and added an example document. I hope that helps.
    As an added bonus, I’ve added a URLDecode function that will translate the title from URL. No more %20. (Supports åäâöéèêëóòô…)

  6. Hi. Great Help!

    But if my Web Dav server requires basic authentication, how can I directly open a document without opening the windows authentication dialog?

    Is there some Open Command with username and pass parameters?

  7. Thanks for this excellent guide! It has helped me tremendously integrating some VBA code with our Wiki (Confluence). How could the above code be modified to rename an existing WebDAV directory?

    Would you recommend updating RESOURCE_PARSENAME and davFiles to perform an ADODB.Recordset update, or davFile and perform an ADODB.Record MoveRecord?

    And for deleting a file/directory, would you simply find the davFile and use the ADODB.Record DeleteRecord function or would you use the davFiles ADODB.RecordSet Delete function?

    I assume that taking the RecordSet approach in either of the above would require closing the davFile first. Using davFile will require closing davFile after the move, but what about after a delete? Does anything need to be done after calling davFile.DeleteRecord other than setting davFile = Nothing (although I notice you never set dirFile = Nothing like you do davFiles and davDir) — is there still something to close after deleting the Record?

  8. @steve
    I tried both approaches for renaming a directory (e.g. both using davFile.MoveRecord and davFiles.Update “RESOURCE_PARSENAME” “NewName”) Both approaches generated unsupported errors. Any suggestions?

    • I started looking into the issue, but since I’m running Windows 7 (x64) I keep getting:
      -2147217895: Object or data matching the name, range, or selection criteria was not found within the scope of this operation.
      This might be caused by new security restrictions, but I’m not sure. If I find a solution I’ll post it here.

  9. I was able to figure out how to rename files/directories using WebDav. Here is some sample code.

    origName is the original name that should be renamed.
    parentPage is the full WebDAV path to where origName should be moved/renamed
    newName is the new name for the file/folder
    davFiles is an ADODB.RecordSet
    davFile is an ADODB.Record

    Do While Not davFiles.EOF
    davFile.Open davFiles, , adModeReadWrite
    If origName = davFile.Fields(“RESOURCE_PARSENAME”).Value Then
    davFile.MoveRecord , parentPage & newName
    davFile.Close
    davFiles.MoveLast
    Else
    davFile.Close ‘ Always remember to Close the file!
    davFiles.MoveNext ‘ Move the index to the next Record.
    End If
    Loop

  10. Hi, this has been most helpful!
    Is it possible to open files returned that use another program, powerpoints, pdfs, excel files etc?

  11. Hi
    thats for the wonderful post
    Just 1 question, is the password encrypted while sending the data to the WEBDAV server.Koz the password is written in plain text in the VBA code.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>