ScottishTapWater
ScottishTapWater

Reputation: 4816

Downloading file from MS teams programatically

Previously, I developed an application which downloaded a file from a corporate Sharepoint site and then performed some magic with it.

The powers that be have since migrated to MS Teams and I'm trying to update the application to use the new platform. However, I'm having all sorts of issues getting the file to download.

My old (working for Sharepoint) code uses a WebClient to retrieve the file based on credentials previously provided by the user:

    private string GetSchedule(string username, string password, string domain)
    {

        string tempPath = Path.GetTempFileName().Replace(".tmp", ".xlsm");
        using (WebClient client = new WebClient())
        {
            client.Credentials = new NetworkCredential(username, password, domain);
            try
            {
                client.DownloadFile(_networkSchedulePath, tempPath);
            }
            catch (WebException e)
            {
                if (e.Message.Contains("401"))
                {
                    StatusUpdated?.Invoke(this, new EventArgs<string>("Invalid Credentials Provided"));
                    Finished?.Invoke(this, null);
                    return null;
                }
                if (e.Message.Contains("404"))
                {
                    StatusUpdated?.Invoke(this, new EventArgs<string>("File Not Found"));
                    Finished?.Invoke(this, null);
                    return null;
                }
                else
                {
                    StatusUpdated?.Invoke(this, new EventArgs<string>(e.Message));
                    Finished?.Invoke(this, null);
                    return null;
                }
            }
        }
        return tempPath;
    }

However, when I use this with the new teams link I'm getting a 403 Forbidden error. So is there any way to programmatically retrieve a file from MS Teams?

Upvotes: 0

Views: 4347

Answers (3)

Markus1980Wien
Markus1980Wien

Reputation: 529

using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
using System.IO;
using System.Linq;

namespace Answer
{
    class Answer
    {
        static void Main(string[] args)
        {
            // Create Confidential Application
            IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
                .Create("<My_Azure_Application_Client_ID>")
                .WithTenantId("<My_Azure_Tenant_ID>")
                .WithClientSecret("<My_Azure_Application_Client_Secret>")
                .Build();

            // Create an authentication provider.
            ClientCredentialProvider authenticationProvider = new ClientCredentialProvider(confidentialClientApplication);
            // Configure GraphServiceClient with provider.
            GraphServiceClient graphServiceClient = new GraphServiceClient(authenticationProvider);

            // Get a user
            var user = graphServiceClient.Users["<My_Azure_User_Name>"].Request().GetAsync().Result;
            
            // Get the teams the user is member of
            var joinedTeams = graphServiceClient.Users[user.Id].JoinedTeams.Request().GetAsync().Result;

            // Get the team we are intereseted in
            var team1 = joinedTeams.FirstOrDefault(t => t.DisplayName == "<TeamName_Of_Interest>");

            // Get the main folders
            var folders = graphServiceClient.Groups[team1.Id].Drive.Root.Children
                .Request()
                .GetAsync().Result;

            // Get the files in the first main folder
            var files = graphServiceClient.Groups[team1.Id].Drive.Items[folders[0].Id].Children
                .Request()
                .GetAsync().Result;

            // Get the file-Data of the first file
            MemoryStream fileData = graphServiceClient.Groups[team1.Id].Drive.Items[files[0].Id].Content
                .Request()
                .GetAsync().Result as MemoryStream;

            // Save the file to the hard-disc
            System.IO.File.WriteAllBytes($"C:\\{files[0].Name}", fileData.ToArray());
        }
    }
}

Upvotes: 1

ScottishTapWater
ScottishTapWater

Reputation: 4816

Thanks to JLRishe for the help his answer and comments provided. However, the final solution varied from the one in his answer, which is why I'm posting it here:


The OfficeDevPnP.Core package is used extensively for this.

Firstly, the AuthenticationManager is used to get a ClientContext in terms of the specific sharepoint site that needs to be accessed. This pops a window up to allow for the MFA. Then various components are loaded in via the ClientContext object. From here, the file is fetched via Guid and dumped to disk.

private string GetSchedule()
{
    string tempPath = Path.GetTempFileName().Replace(".tmp", ".xlsm");
    try
    {
        AuthenticationManager authManager = new OfficeDevPnP.Core.AuthenticationManager();
        ClientContext ctx = authManager.GetWebLoginClientContext("https://oursite.sharepoint.com/sites/ourspecificsite/");
        Web web = ctx.Web;
        Microsoft.SharePoint.Client.File schedule = web.GetFileById(new Guid("ourguid"));
        ctx.Load(web);
        ctx.Load(schedule);
        ctx.ExecuteQuery();
        FileInformation fInfo = Microsoft.SharePoint.Client.File.OpenBinaryDirect(ctx, schedule.ServerRelativeUrl);
        using (var fileStream = File.Create(tempPath))
        {
            fInfo.Stream.CopyTo(fileStream);
        }
    }
    catch (WebException e)
    {
        StatusUpdated?.Invoke(this, new EventArgs<string>(e.Message));
        return null;
    }

    return tempPath;
}

Upvotes: 0

JLRishe
JLRishe

Reputation: 101690

I was mistaken in the comments. Simply replacing the NetworkCredentials with SharePointOnlineCredentials is not the solution.

I'm not sure if the following is the "right" approach, but it works and seems pretty solid. Please give it a try:

private static string GetFile(string path, string username, string password, string domain)
{
    var secureString = new SecureString();
    foreach (var ch in password)
    {
        secureString.AppendChar(ch);
    }

    string tempPath = Path.GetTempFileName().Replace(".tmp", ".xlsm");
    using (WebClient client = new WebClient())
    {
        var credentials = new SharePointOnlineCredentials(username, secureString);

        client.Headers[HttpRequestHeader.Cookie] = credentials.GetAuthenticationCookie(new Uri(path));
        try
        {
            client.DownloadFile(path, tempPath);
        }
        catch (WebException e)
        {
            // Error Handling
        }
    }
    return tempPath;
}

Another option is to use the CSOM rather than using a webclient directly. n.b., I encountered errors at the OpenBinaryDirect() call when using the Microsoft.SharePoint.Client NuGet package and it looks like this package is wildly out of date. It appears that the one to use now is Microsoft.SharePointOnline.CSOM or Microsoft.SharePoint2019.CSOM:

private static string GetFileWithClientContext(string path, string username, string password, string domain)
{
    var secureString = new SecureString();
    foreach (var ch in password)
    {
        secureString.AppendChar(ch);
    }
    string tempPath = Path.GetTempFileName().Replace(".tmp", Path.GetExtension(path));
    using (var context = new ClientContext(path))
    {
        context.Credentials = new SharePointOnlineCredentials(username, secureString);

        try
        {
            using (var file = Microsoft.SharePoint.Client.File.OpenBinaryDirect(context, new Uri(path).AbsolutePath))
            using (var outFile = System.IO.File.OpenWrite(tempPath))
            {
                file.Stream.CopyTo(outFile);
            }
        }
        catch (WebException e)
        {
            // Error Handling
        }
    }
    return tempPath;
}

Upvotes: 1

Related Questions