7

I have an Excel workbook that has an active data connection to a SharePoint list on a company server. The SP list is just a listing of all the files in an SP document library at that point in time. I have a VBA subroutine that is responsible for refreshing this data connection to see what is in the library at that time and then move some info from the list (document name, document author, submission timestamp, etc.) to a different workbook.

The SharePoint site uses Active Directory credentials to authenticate and the SharePoint is also mapped as a network drive on the PC running the code. But even so, refreshing this data connection sometimes results in a credential prompt that looks just like the image at the end of my post. If I manually enter the same AD credentials again, the connection request is authenticated and the list updates in Excel.

My question is this: how can I account for this in my code? Ideally, I would like for this to trigger an email alert or something, but the thing is that the line of code (ThisWorkbook.RefreshAll) that performs the connection refresh does not run to completion until the credential prompt is dealt with, so I can't set up any handlers in the lines of code that follow. I can't have this refresh potentially resulting in code that just hangs on this line until someone happens to notice something is wrong (it is running on an unattended PC). Anyone know anything that could help deal with my issue?

enter image description here

TylerH
  • 20,799
  • 66
  • 75
  • 101
KOstvoll
  • 133
  • 1
  • 1
  • 6
  • VBA is single threaded. It sounds like you need a multi-threaded application that performs this work. If you wrote a .NET application that performed the data refresh using the Excel application COM object then you could have a timeout thread that takes action if the refresh command doesn't return within the time limit you specify. You could do anything you want from there. – HackSlash Apr 01 '20 at 18:32
  • If you want to run this unattended in the background I would take it a step further and make that application run as a windows service. This way it can be automatically started and maintained like any other background service. VBA is not designed to be run unattended and you should not run a long loop like this for the reasons you have run in to. – HackSlash Apr 01 '20 at 18:36
  • I was just wondering: you can write an errorHandler, and in there, use sendkeys to type username and password in login pop up. – Shivang Gupta Apr 03 '20 at 14:19
  • 1
    @ShivangGupta You could, but then you'd be giving unencrypted access to a network/O365 login to anyone with access to the machine where the VBA file is stored. So, not really an acceptable workaround, unfortunately. – TylerH Apr 03 '20 at 14:34
  • @TylerH, password protecting the module in which the sub procedure resides, will be helpful. – Shivang Gupta Apr 03 '20 at 14:41
  • 2
    @ShivangGupta A little, but then you also have to account for that in the code, and password-protected Excel files [can be cracked](https://stackoverflow.com/a/31005696/2756409). Even as a workaround, storing network/Office 365 account credentials in plaintext should not ever be done by anyone outside of a test/demo environment. – TylerH Apr 03 '20 at 14:49

2 Answers2

0

Since the drive is locally mapped, you should be able to just go directly to the file and manipulate it however you need, importing it, instead of having an active data connection. It would allow you more flexibility than a more rigid data connection.

This website has a good example showing how to do what you're looking for, but the way I'm imagining would be more efficient considering the circumstances.

Benjamin
  • 9
  • 1
  • 4
    The linked article doesn't seem to be related at all to refreshing a data connection or handling a prompt for credentials (or avoiding it). Additionally, for what it's worth, I'll point out that the solution you gave doesn't apply at all to the bounty reason. Even for this case, files in SharePoint are stored as blobs so I don't really know what OP is talking about there WRT mapping the SharePoint server (site?) to a mapped drive. – TylerH Apr 02 '20 at 21:24
0

This really depends on how you are doing your connection and in some instances it is not possible, but you can append Username and Password to a URL to pass your credentials, such as defined here (for other languages but you get the gist):

https://www.connectionstrings.com/sharepoint/

Now the reality is, you probably aren't doing a REST connection and you might have to as discussed here: https://www.experts-exchange.com/questions/28628642/Excel-VBA-code-using-authentication-to-SharePoint.html

They recommended:

Public Sub CopyToSharePoint()
On Error GoTo err_Copy

Dim xmlhttp
Dim sharepointUrl
Dim sharepointFileName
Dim tsIn
Dim sBody
Dim LlFileLength As Long
Dim Lvarbin() As Byte
Dim LobjXML As Object
Dim LstrFileName As String
Dim LvarBinData As Variant
Dim PstrFullfileName As String
Dim PstrTargetURL As String
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Dim fldr As Folder
Dim f As File
Dim pw As String
Dim UserName As String
Dim RetVal
Dim I As Integer
Dim totFiles As Integer
Dim Start As Date, Finish As Date

UserName = InputBox(Username?") pw = InputBox("Password?")

sharepointUrl = "[http path to server]/[server folder to write to]"

Set LobjXML = CreateObject("Microsoft.XMLHTTP")

Set fldr = fso.GetFolder(CurrentProject.Path & "\[folder with files to
upload]\") totFiles = fldr.Files.Count

For Each f In fldr.Files

  sharepointFileName = sharepointUrl & f.Name

'****************************   Upload text files 
**************************************************

  If Not sharepointFileName Like "*.gif" And Not sharepointFileName
Like "*.xls" And Not sharepointFileName Like "*.mpp" Then

    Set tsIn = f.OpenAsTextStream
    sBody = tsIn.ReadAll
    tsIn.Close
  
    Set xmlhttp = CreateObject("MSXML2.XMLHTTP.4.0")
    xmlhttp.Open "PUT", sharepointFileName, False, UserName, Password
    xmlhttp.Send sBody
       Else

'****************************   Upload binary files 
**************************************************
  
    PstrFullfileName = CurrentProject.Path & "\[folder with files to upload]\" & f.Name
    LlFileLength = FileLen(PstrFullfileName) - 1

    ' Read the file into a byte array.
    ReDim Lvarbin(LlFileLength)
    Open PstrFullfileName For Binary As #1
    Get #1, , Lvarbin
    Close #1

    ' Convert to variant to PUT.
    LvarBinData = Lvarbin
    PstrTargetURL = sharepointUrl & f.Name


    ' Put the data to the server, false means synchronous.
    LobjXML.Open "PUT", PstrTargetURL, False, Username, Password

   ' Send the file in.
    LobjXML.Send LvarBinData

  End If
     I = I + 1   RetVal = SysCmd(acSysCmdSetStatus, "File " & I & " of " & totFiles & " copied...")    Next f

  RetVal = SysCmd(acSysCmdClearStatus)   Set LobjXML = Nothing   Set
fso = Nothing


err_Copy: If Err <> 0 Then   MsgBox Err & " " & Err.Description End If

End Sub 

Realistically, I think this answer may get you going down the right road: https://sharepoint.stackexchange.com/questions/255264/sharepoint-api-and-vba-access-denied

Regardless, this is a problem and good luck. I had better luck using MS Access to link the list as a table and then using Excel to just call Access and get what I needed.


Private Sub cmdSyncSP_Click()
On Error GoTo ErrorCode
    Application.Cursor = xlWait
    Dim app As New Access.Application
    'Set app = CreateObject("Application.Access")
    app.OpenCurrentDatabase Application.ActiveWorkbook.Path & "\SP_Sync.accdb"
    app.Visible = False
    app.Run "doManualCheck"
    app.CloseCurrentDatabase
    Set app = Nothing
    MsgBox "Sync has finished.  Refresh and proceed to copy your data.", vbInformation + vbOKOnly, "Success"
ExitCode:
    On Error Resume Next
    Application.Cursor = xlDefault
    Exit Sub
ErrorCode:
    MsgBox "Error " & Err.Number & ": " & Err.Description, vbCritical, "Sync Error"
    Resume ExitCode
End Sub
Community
  • 1
  • 1
Munsterlander
  • 1,356
  • 1
  • 16
  • 29
  • 1
    As with the suggestion in the comments, putting an Office 365 licensed password in plain text is not really a solution. Likewise, prompting a user for credentials doesn't work for a process that is supposed to be automated (read: unattended). – TylerH Apr 08 '20 at 15:28
  • As for REST, you're right that I'm not using it (I'm using a ListObject from an .iqy file downloaded from a SharePoint list). However, REST shouldn't be required as the refresh *does* work in Office 365 (2016 version) in my dev environment, just not in Office 2010 in prod. I admit I just now realized I forgot to mention the Office version in my bounty reason... unfortunately I think that was probably crucial information as my current hypothesis is that the connection requires modern authentication now which Office 2010 doesn't support at all (and 2013 only supports partially). – TylerH Apr 08 '20 at 15:31
  • Right. Thats why I used Access. It seems to hold the credentials better. Then in Excel I called Access to do what I needed. It might be your only option. We are facing a similar issue with SSO and SharePoint as we migrate to O365. – Munsterlander Apr 08 '20 at 15:36
  • Hmm, maybe trying to keep the credentials valid might be a better approach than having to send them. I haven't tested this, but maybe this approach would help. https://kb.intermedia.net/article/1668. E.g. map as a network drive and follow steps mentioned to stay authenticated. Once mapped, it *should* operate as a normal Drive. You still have to deal with credentials expiring, but I think that's pretty much unavoidable. – Ryan Wildry Apr 09 '20 at 13:12
  • That might be viable, but that will need to be done on every client machine - if I read it correctly. I highly recommend trying the Access route. There is just something about it that it does differently where we have never encountered that issue. Setup the list as a remote connection for a table. I think at one point we were virtually creating the access file so we knew it got created. Then Excel can read the data direct from there. – Munsterlander Apr 09 '20 at 13:44