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}");
        }
    }
}



No comments:

Post a Comment

Featured Post

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 → Micros...

Popular posts