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