Skip to content

Instantly share code, notes, and snippets.

Last active October 17, 2023 18:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save luckerby/3eb3bc6f0910c9d72a2a0d79f22e765e to your computer and use it in GitHub Desktop.
Save luckerby/3eb3bc6f0910c9d72a2a0d79f22e765e to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
namespace RetrieveO365PhotosAndInjectToStorage
class Program
static async Task Main(string[] args)
// The full path to where this process' image started from will be
// used to store the photos when writing to disk
string pathToCurrentProcess = System.IO.Directory.GetCurrentDirectory();
// Build a client application
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
// Create a new instance of GraphServiceClient with the authentication provider.
// Using beta endpoint for now since it's only here where the photos can be retrieved successfully both
// for on-prem as well as cloud users
GraphServiceClient graphClient = new GraphServiceClient("", authProvider);
// == Retrieve the list of users from Azure AD ==
// Build the request that will retrieve the users with enabled accounts
var userRequest = graphClient.Users
.Filter("accountEnabled eq true");
// Setup the hash that will contain the pairs of user id and its corresponding employeeNumber
Dictionary<string, string> userIdsAndEmployeeNumber = new Dictionary<string, string>();
// Process each page of the user result set
var users = await userRequest.GetAsync();
foreach (var user in users)
// the Graph API will return this as a string
string employeeNumber = null;
// There are enabled accounts that don't have this extension (as
// there's no backing employee number), so use try/catch
employeeNumber = (string)user.AdditionalData["extension_4922b266f3ce4f7ea0ba403a0bca8fc0_employeeNumber"];
if (employeeNumber != null)
userIdsAndEmployeeNumber.Add(user.Id, employeeNumber);
Console.WriteLine($"{user.DisplayName} [no:{employeeNumber}]");
// Switch to the next page
userRequest = users.NextPageRequest;
//while (userRequest != null);
// == Azure setup ==
var connectionString = "DefaultEndpointsProtocol=https;AccountName=office365photosstorage;AccountKey=8uv+JdD6aBHWZH92hs1lMISOnqMkYdQ1PAJ4Dm5mM391x3hhZeaXTH3lEyEEEJ+oGsKkpJiNvL2X6kLxYCS2Fg==;";
// Build the object that will connect to the container within our target storage account
BlobContainerClient blobContainerClient = new BlobContainerClient(connectionString, "photos");
// == Pull the photos using Graph ==
// Retrieve the photos for the list of users built previously
foreach (string userId in userIdsAndEmployeeNumber.Keys)
Stream photoStream = default;
Task<Stream> photoTask = graphClient.Users[userId].Photo.Content.Request().GetAsync();
photoStream = await photoTask;
catch (Microsoft.Graph.ServiceException)
// We'll end up here if the photo isn't found stamped on the user,
// but we do nothing since photoStream will be left at its default (null)
// value
if (photoStream != null)
string filenameToWrite = userIdsAndEmployeeNumber[userId] + ".jpg";
string pathToFilenameToWrite = pathToCurrentProcess + '\\' + filenameToWrite;
// Write to local disk
await using (System.IO.FileStream fileStream =
new FileStream(pathToFilenameToWrite, System.IO.FileMode.Create))
await photoStream.CopyToAsync(fileStream);
// Rewind the position within the stream, otherwise we'll deadlock next
photoStream.Position = 0;
// Write to Azure Blob Storage
BlobClient blobClient = blobContainerClient.GetBlobClient(filenameToWrite);
await blobClient.UploadAsync(photoStream, true);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment