Tuesday, November 4, 2025

How to Upload User Profile Photos to Azure AD (Microsoft Entra ID) Using C# and Microsoft Graph API

How to Upload User Profile Photos to Azure AD (Microsoft Entra ID) Using C# and Microsoft Graph API

// To DO:
// Go to Azure Portal → Microsoft Entra ID → App registrations
// Find your app:
// Go to API permissions
// Check if you have: Microsoft Graph → User.ReadWrite.All (Application permission)
// If missing, click Add a permission → Microsoft Graph → Application permissions → Search and add User.ReadWrite.All
// Click "Grant admin consent" - This is crucial!

using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Extensions.Configuration;

namespace AzureADPhotoUploader;

class Program
{
    static async Task Main(string[] args)
    {
        try
        {
            Console.WriteLine("Azure AD Photo Uploader");
            Console.WriteLine("=======================\n");

            // Load configuration
            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddEnvironmentVariables()
                .Build();

            var tenantId = configuration["AzureAd:TenantId"];
            var clientId = configuration["AzureAd:ClientId"];
            var clientSecret = configuration["AzureAd:ClientSecret"];

            if (string.IsNullOrEmpty(tenantId) || string.IsNullOrEmpty(clientId))
            {
                Console.WriteLine("Error: Azure AD configuration is missing. Please check appsettings.json");
                return;
            }

            // Initialize Graph Client with appropriate authentication
            Console.WriteLine("Initializing Microsoft Graph client...");
            GraphServiceClient graphClient = CreateGraphClient(tenantId, clientId, clientSecret);
            Console.WriteLine("✓ Graph client initialized\n");

            // Get user ID or UPN to upload photo
            Console.Write("Enter user ID or User Principal Name (UPN): ");
            var userId = Console.ReadLine();

            if (string.IsNullOrWhiteSpace(userId))
            {
                Console.WriteLine("Error: User ID or UPN is required.");
                return;
            }

            // Get photo file path
            Console.Write("Enter the full path to the photo file (JPG/PNG): ");
            var photoPath = Console.ReadLine();

            if (string.IsNullOrWhiteSpace(photoPath) || !File.Exists(photoPath))
            {
                Console.WriteLine("Error: Photo file not found.");
                return;
            }

            // Upload the photo
            await UploadUserPhoto(graphClient, userId, photoPath);

            Console.WriteLine("\nPress any key to exit...");
            Console.ReadKey();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"\nUnexpected error: {ex.Message}");
            Console.WriteLine($"Stack trace: {ex.StackTrace}");
        }
    }

    /// <summary>
    /// Creates a GraphServiceClient with appropriate authentication method
    /// Uses ClientSecretCredential for service principal authentication
    /// For production, consider using Managed Identity when hosted in Azure
    /// </summary>
    static GraphServiceClient CreateGraphClient(string tenantId, string clientId, string? clientSecret)
    {
        // Use ClientSecretCredential for daemon/service applications
        // For interactive user applications, use InteractiveBrowserCredential
        // For Azure-hosted apps, use DefaultAzureCredential (Managed Identity)
       
        if (!string.IsNullOrEmpty(clientSecret))
        {
            // Service Principal authentication (daemon apps)
            var clientSecretCredential = new ClientSecretCredential(
                tenantId,
                clientId,
                clientSecret
            );
           
            return new GraphServiceClient(clientSecretCredential);
        }
        else
        {
            // Interactive Browser authentication (user apps)
            Console.WriteLine("Using interactive browser authentication...");
            var interactiveBrowserCredential = new InteractiveBrowserCredential(
                new InteractiveBrowserCredentialOptions
                {
                    TenantId = tenantId,
                    ClientId = clientId,
                    RedirectUri = new Uri("http://localhost")
                }
            );
           
            return new GraphServiceClient(interactiveBrowserCredential);
        }
    }

    /// <summary>
    /// Uploads a photo to a user's Azure AD profile
    /// </summary>
    /// <param name="graphClient">The authenticated Graph Service Client</param>
    /// <param name="userId">User ID or User Principal Name (UPN)</param>
    /// <param name="photoPath">Full path to the photo file</param>
    static async Task UploadUserPhoto(GraphServiceClient graphClient, string userId, string photoPath)
    {
        try
        {
            Console.WriteLine($"\nUploading photo for user: {userId}");
            Console.WriteLine($"Photo file: {photoPath}");

            // Validate file size (Azure AD supports photos up to 4MB for user profiles)
            var fileInfo = new FileInfo(photoPath);
            Console.WriteLine($"File size: {fileInfo.Length / 1024.0:F2} KB");
           
            if (fileInfo.Length > 4 * 1024 * 1024)
            {
                Console.WriteLine("Error: Photo file size exceeds 4MB limit.");
                return;
            }

            // Validate file type
            var extension = Path.GetExtension(photoPath).ToLowerInvariant();
            Console.WriteLine($"File extension: {extension}");
           
            if (extension != ".jpg" && extension != ".jpeg" && extension != ".png")
            {
                Console.WriteLine("Error: Only JPG and PNG formats are supported.");
                return;
            }

            // First, try to verify the user exists
            try
            {
                Console.WriteLine("\nVerifying user exists...");
                var user = await graphClient.Users[userId].GetAsync();
                Console.WriteLine($"✓ User found: {user?.DisplayName} ({user?.UserPrincipalName})");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"✗ Could not verify user: {ex.Message}");
                Console.WriteLine("Continuing with photo upload anyway...");
            }

            // Read the photo file
            Console.WriteLine("\nReading photo file...");
            using var photoStream = File.OpenRead(photoPath);
            Console.WriteLine($"Photo stream length: {photoStream.Length} bytes");
           
            // Upload the photo
            // The Graph API expects the photo content in the request body
            Console.WriteLine("Uploading to Microsoft Graph API...");
            await graphClient.Users[userId].Photo.Content
                .PutAsync(photoStream);

            Console.WriteLine("✓ Photo uploaded successfully!");

            // Optional: Verify the upload by retrieving photo metadata
            var photoMetadata = await graphClient.Users[userId].Photo.GetAsync();
            if (photoMetadata != null)
            {
                Console.WriteLine($"\nPhoto Details:");
                Console.WriteLine($"  Height: {photoMetadata.Height}px");
                Console.WriteLine($"  Width: {photoMetadata.Width}px");
            }
        }
        catch (Microsoft.Graph.Models.ODataErrors.ODataError odataError)
        {
            Console.WriteLine($"\nGraph API Error: {odataError.Error?.Message}");
            Console.WriteLine($"Code: {odataError.Error?.Code}");
           
            // Display additional error details
            if (odataError.Error?.InnerError != null)
            {
                Console.WriteLine($"Inner Error: {odataError.Error.InnerError.AdditionalData}");
            }
           
            if (odataError.ResponseStatusCode > 0)
            {
                Console.WriteLine($"HTTP Status Code: {odataError.ResponseStatusCode}");
            }
           
            // Provide specific guidance based on error code
            if (odataError.Error?.Code == "Authorization_RequestDenied")
            {
                Console.WriteLine("\nTip: Ensure your app registration has 'User.ReadWrite.All' permission");
                Console.WriteLine("and that admin consent has been granted.");
            }
            else if (odataError.Error?.Code == "UnknownError")
            {
                Console.WriteLine("\nPossible causes:");
                Console.WriteLine("1. The photo size might be incompatible (try resizing to 648x648 pixels)");
                Console.WriteLine("2. Missing API permissions - ensure 'User.ReadWrite.All' is granted");
                Console.WriteLine("3. The user might not exist or the UPN is incorrect");
                Console.WriteLine("4. The app might not have admin consent");
                Console.WriteLine("5. The photo stream might be corrupted or in an unsupported format");
            }
           
            // Display full exception for debugging
            Console.WriteLine($"\nFull error details: {odataError}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"\nError uploading photo: {ex.Message}");
            Console.WriteLine($"Exception type: {ex.GetType().Name}");
            Console.WriteLine($"Stack trace: {ex.StackTrace}");
        }
    }
}



3 comments:

  1. Stumbling upon a trustworthy NewsMetroPolitan has transformed my news consumption habits. The writing is clear, sources are credible, and coverage is comprehensive. Thank you for maintaining such exceptional standards!

    ReplyDelete
  2. I particularly value how NewsDataCenter balances hard news with cultural insights beautifully. The technology section is informative, and political analysis is always thought-provoking. Superb hub that delivers consistently!

    ReplyDelete
  3. So grateful to have NewsEvidence as a trusted news resource. Their rigorous fact-checking process and commitment to journalistic integrity ensure that every article published provides readers with reliable, verified information they can confidently share.

    ReplyDelete

Featured Post

Automate SharePoint List CRUD Operations Using PowerShell, Microsoft Graph API & GitHub Actions

Automate SharePoint List CRUD Operations Using PowerShell, Microsoft Graph API & GitHub Actions App-only Auth with Certificate   |  ...

Popular posts