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