2

I've had success using VBA in Excel to map a drive to extranet SharePoint to download files, however in deployment it works in one location but not another (different environments possible). I'm curious if anyone has any insight as to what user or system setting would cause this.

In the code below I try to map the drive to SharePoint, if it errors out the handler creates a new instance of excel and saves it to the SharePoint site. By nature this forces IE to open and prompt the user for their login details, once submitted it authenticates them and uploads the file. They are then able to map the drive to SharePoint. The problem I'm having with the one group is it will upload the file, however they do not stay authenticated to map the drive. Even weirder, the user is logged into the SharePoint site in IE while I'm stepping through this procedure.

Sub MapSharePoint()
    Dim objNet as object
    Dim strDriveLetter as String
    Dim strSharePointDatabaseFolder as String
    Set objNet = CreateObject("WScript.Network")

    On Error GoTo AUTH_Connection:
    strDriveLetter = <function to find open drive>
    strSharePointDatabaseFolder = <SharePoint site>
    objNet.MapNetworkDrive strDriveLetter, strSharePointDatabaseFolder

    <do something with mapped drive> 

    Exit Sub

AUTH_Connection:

    Dim xlApp As New Excel.Application
    Dim xlDoc As Workbook
    On Error GoTo ErrHandler:

    Set xlApp = CreateObject("Excel.Application")
    Set xlDoc = xlApp.Workbooks.Add
    ' Trying to upload the file below will force IE to open and prompt user for their Username and Password which will authenticate them
    xlDoc.SaveAs FileName:="<SharePointSite>", FileFormat:=xlWorkbookNormal, AddToMru:=False
    xlDoc.Close
    xlApp.Quit

    objNet.MapNetworkDrive strDriveLetter, strSharePointDatabaseFolder
    Resume Next
ErrHandler:
    MsgBox Err.Code, Err.Description

End Sub

UPDATE 1:

Using the code below the problem I'm running into is the SharePoint authentication. In the catch brackets I added the line of code below to pop a message window with the specific error text, and was getting 403: Forbidden. After download Fiddler I can see that the site is using an authentication cookie, which I've read WebClient does not support. I've been trying to capture the cookie and authenticate using it, so now I don't get the 403 error, but instead I'm downloading the HTML code from the web form login. I need to figure out how to send a login request, capture the auth cookie that comes back, and then use that when sending the DownloadFile request.

System.Windows.Forms.MessageBox.Show(ex.Message);
pheeper
  • 1,457
  • 4
  • 20
  • 37
  • I had a similar issue and ended up having to resort to C# in order to use: System.Net.NetworkCredentials – SlowLearner Jun 24 '16 at 20:08
  • The reason behind this may relay on the network security, I had a problem like it; once the user is auth the "original" IE object is "destroyed" -then created again with the credentials-, hence, it's never going to knew that the new "IE" should be analyzed instead. I couldn't find a way to solve it. – Sgdva Jun 24 '16 at 21:47
  • 1
    @Sgdva yes, that was the problem as I understood it. It was not possible for VBA to use the credentials from IE in a reliable way, and some people preferred to use other browsers and everything just stops working... hence I ended up resorting to C#... it was a killer learning curve :-/ – SlowLearner Jun 24 '16 at 22:03
  • 1
    Phillip @Sgdva theres some more background info here: [SO 32697709](http://stackoverflow.com/questions/32697709). Also, that thread uses VB.Net - for the life of me, I can't remember why I didn't stick with the VB.Net solution. I suspect it worked but lacked flexibility, or possibly required the dll to be registered in the GAC – SlowLearner Jun 26 '16 at 20:43
  • @SlowLearner Incredible research! I'll try this/that approach when I re take that SP project, saved in favs! – Sgdva Jun 27 '16 at 13:20
  • @Sgdva thanks for the feedback, good luck with SP, feel free to up-vote that other article if it helps ;-) – SlowLearner Jun 27 '16 at 22:13
  • @SlowLearner faved and up voted as well! – Sgdva Jun 28 '16 at 02:38
  • :) tnx glad it helping in some way @sgdva – SlowLearner Jun 28 '16 at 10:14

1 Answers1

2

For what its worth, here is the code that I ended up using. I was easier to learn enough C# (first time using C#) to do this than trying to figure it out with VBA. Arguments (files to download) are passed as a string and split to an array. Hope it helps.

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using RGiesecke.DllExport;
using System.Windows.Forms;

namespace sptHELPER { 
public class sptDL
{
    [DllExport("getResources", System.Runtime.InteropServices.CallingConvention.StdCall)]
    public static Int32 sptDownLoader(string sptURL, string sptItem, string sptTemp, string sptUser = "", string sptPass = "")
    {
        //System.Windows.Forms.MessageBox.Show("In function");
        int Result = 0;
        Result = 0;

        System.Net.NetworkCredential myCredentials = new System.Net.NetworkCredential();

        if (string.IsNullOrEmpty(sptUser))
        {
            myCredentials = System.Net.CredentialCache.DefaultNetworkCredentials;
        }
        else
        {
            myCredentials.UserName = sptUser;
            myCredentials.Password = sptPass;
            myCredentials.Domain = "";
        }

        // set a temporary Uri to catch an invalid Uri later
        Uri mySiteSP = new Uri("http://www.defaultfallback");

        string myFile = null;

        int iCount = 0;
        string[] arr1 = sptItem.Split('*');
        arr1 = sptItem.Split('*');

        StandAloneProgressBar sp = new StandAloneProgressBar();

        for (iCount = arr1.GetLowerBound(0); iCount <= arr1.GetUpperBound(0); iCount++)
        {
            try
            {
                myFile = arr1[iCount];
                mySiteSP = new Uri(sptURL + "/" + myFile);
                string dest = sptTemp + "/" + myFile;
                dest = dest.Replace("/", "\\") ;
                //System.Windows.Forms.MessageBox.Show(dest + " " + sptURL + "/" + myFile);
                System.Net.WebClient mywebclient = new System.Net.WebClient();
                mywebclient.Credentials = myCredentials;
                mywebclient.DownloadFile(mySiteSP, dest);
            }

            catch (Exception ex)
            {
                Result = ex.HResult;
                break; 
            }
        }
        return Result;
    }
}
}

In VBA add a module with the following code, modified to suit your needs:

Option Explicit

#If VBA7 Then ' Office 2010 or later (32/64 Bit )...
Private Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare PtrSafe Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Private Declare PtrSafe Function sptDL Lib "sptHELPER.dll" Alias "getResources" (ByVal sptURL As String, ByVal sptItem As String, ByVal sptTemp As String, ByVal sptUser As String, ByVal sptPass As String) As Integer
#Else
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Private Declare Function sptDL Lib "sptHELPER.dll" Alias "getResources" (ByVal sptURL As String, ByVal sptItem As String, ByVal sptTemp As String, ByVal sptUser As String, ByVal sptPass As String) As Integer
#End If

Private Type sptSP_Data
    sptURL As String
    sptResourceNames As String
    sptUserName As String
    sptPassWord As String
    sptdomain As String
    sptDestination As String
End Type

' Purpose:
' Get resources from sharepoint (or Website)
Function getSharePointItems() As Boolean

    Dim strTemp As String
    Dim strRes() As String
    Dim lLib As Long
    Dim result As Double ' get error code
    Dim sptData As sptSP_Data ' Private Type Above

    ' 1. SharePoint Settings
    sptData.sptURL = "<SharepointURL>" ' e.g. "http://testsp-mysite.cloudapp.net/sites/spTesting/"
    sptData.sptUserName = "<UserName>"
    sptData.sptPassWord = "<PassWord>"
    sptData.sptdomain = "<Domain>" ' I left this blank
    sptData.sptResourceNames = "strRes1*strRes2*strRes3*strRes4*strRes5"
    sptData.sptDestination = "<PathToSaveTo>" ' should already be created

    ' Use sptHELPER to fetch Resources
    lLib = LoadLibrary(ThisWorkbook.Path & "\sptHELPER.dll")
    result = sptDL(sptData.sptURL, sptData.sptResourceNames, sptData.sptDestination, sptData.sptUserName, sptData.sptPassWord)
    Debug.Print result
    FreeLibrary (lLib)

    ' See if we were sucessful
    Select Case result
        Case 0
             ' All good
        Case 5385 ' Bad URL or No response from the WebServer
            Debug.Print "Bad URL or No response from the WebServer"

        Case 5431 ' URL is empty or not a valid format
            Debug.Print "URL is empty or not a valid format, missing http://"

        Case Else
            ' unknown error
            Debug.Print "Error: " & result & " in getSharePointItems"
    End Select

End Function
SlowLearner
  • 3,086
  • 24
  • 54
  • Thanks for the response and code, but I'm a little confused. Is this then complied into a DLL file which is saved on the users computer and called from VBA? Can you explain this process a little more in your response above? – pheeper Jun 24 '16 at 20:51
  • @Phillip are you already familiar with calling functions like loadLibrary and freeLibrary? If not, they are generally available on Windows machines and usage is well documented. – SlowLearner Jun 24 '16 at 22:15
  • 1
    @SlowLearner Did you use the [XLL add in](https://xll.codeplex.com) for it? – Sgdva Jun 25 '16 at 04:51
  • 1
    @Sgdva no, I used Visual Studio with RGiesecke.DllExport, but XLL addin looks interesting... – SlowLearner Jun 25 '16 at 07:37
  • @SlowLearner I'm not familiar with loading and releasing libraries, but I'm sure I can figure it. Thanks for additional details above, I'll try this out when I'm back in the office on Monday. That XLL add in looks interesting as well, thanks for sharing. – pheeper Jun 25 '16 at 14:17
  • @SlowLearner I'm trying to test this however I keep an error that it can't find the DLL entry point. I've been trying to figure this out but haven't had much luck, any idea what may be causing that? – pheeper Jun 28 '16 at 18:31
  • @SlowLearner still having issues, but once this is created can I distribute to users for use without them having admin rights on their computer? – pheeper Jun 28 '16 at 22:21
  • Are you using the package: using RGiesecke.DllExport; ? I found it on NuGet, check all the names of your functions etc. The other thing I can suggest is start simple.... make your DLL with the bare minimum then add anything else you need.... (I made a test dll that added 2 numbers as my starting point...). I can not say for certain about how it will behave on other computers... for me it works but I am using an installer to add the dll under admin rights. – SlowLearner Jun 28 '16 at 22:49
  • I think the problem is that is I'm not installing the DLL, I'm just dropping it in a folder and referencing to it. Unfortunately for this build I will not be able to get admin rights on the machines to deploy the solution so I need a work around for that. I'll update this if I figure out a solution. – pheeper Jul 05 '16 at 19:52
  • Man, I feel your frustration. I went through weeks of pain trying to figure this out. Can you post a sample of your code? Can you share a simple dll for testing? – SlowLearner Jul 05 '16 at 22:48
  • Check out these SO questions (if you haven't already): http://stackoverflow.com/questions/13980912/cant-find-dll-entry-point & http://stackoverflow.com/questions/30125108/vba-cant-find-c-sharp-dll-entry-point & http://stackoverflow.com/questions/3534600/what-does-register-for-com-interop-actually-do & http://stackoverflow.com/questions/3699767/register-for-com-interop-vs-make-assembly-com-visible - I've trawled back through my notes... you might be missing a .tbl file... hope it helps. Cheers – SlowLearner Jul 05 '16 at 22:58
  • I'm running into an error that says I first need to login to SharePoint through the form. Did you ever run into this? Do I need to pass the credentials to the main site, then download? Regarding the code, I can't share the SP site, but I used your code verbatim, compiled the project, copied and pasted the DLL in the folder with the Excel file, and then changed the Private Declare string to the DLL location. Thanks for the links above I'll check them out. On a side note I'm finding C# is not as intimidating as I thought – pheeper Jul 06 '16 at 14:25
  • No, I have not seen that before. I tested my DLL on Azure SP Farms x2, SP Trial on SBS2008 and in a corporate eviron with in a development area before going live... that said, the user profile I used for testing ALWAYS had some sort of authority to use SP prior.... – SlowLearner Jul 06 '16 at 20:51