diff --git a/SharepointToolbox/Services/Export/UserAccessCsvExportService.cs b/SharepointToolbox/Services/Export/UserAccessCsvExportService.cs
new file mode 100644
index 0000000..6a4577d
--- /dev/null
+++ b/SharepointToolbox/Services/Export/UserAccessCsvExportService.cs
@@ -0,0 +1,145 @@
+using System.IO;
+using System.Text;
+using SharepointToolbox.Core.Models;
+
+namespace SharepointToolbox.Services.Export;
+
+///
+/// Exports user access audit results to CSV format.
+/// Produces one CSV file per audited user with a summary section at the top.
+///
+public class UserAccessCsvExportService
+{
+ private const string DataHeader =
+ "\"Site\",\"Object Type\",\"Object\",\"URL\",\"Permission Level\",\"Access Type\",\"Granted Through\"";
+
+ ///
+ /// Builds a CSV string for a single user's access entries.
+ /// Includes a summary section at the top followed by data rows.
+ ///
+ public string BuildCsv(string userDisplayName, string userLogin, IReadOnlyList entries)
+ {
+ var sb = new StringBuilder();
+
+ // Summary section
+ var sitesCount = entries.Select(e => e.SiteUrl).Distinct().Count();
+ var highPrivCount = entries.Count(e => e.IsHighPrivilege);
+
+ sb.AppendLine($"\"User Access Audit Report\"");
+ sb.AppendLine($"\"User\",\"{Csv(userDisplayName)} ({Csv(userLogin)})\"");
+ sb.AppendLine($"\"Total Accesses\",\"{entries.Count}\"");
+ sb.AppendLine($"\"Sites\",\"{sitesCount}\"");
+ sb.AppendLine($"\"High Privilege\",\"{highPrivCount}\"");
+ sb.AppendLine($"\"Generated\",\"{DateTime.Now:yyyy-MM-dd HH:mm:ss}\"");
+ sb.AppendLine(); // Blank line separating summary from data
+
+ // Data rows
+ sb.AppendLine(DataHeader);
+ foreach (var entry in entries)
+ {
+ sb.AppendLine(string.Join(",", new[]
+ {
+ Csv(entry.SiteTitle),
+ Csv(entry.ObjectType),
+ Csv(entry.ObjectTitle),
+ Csv(entry.ObjectUrl),
+ Csv(entry.PermissionLevel),
+ Csv(entry.AccessType.ToString()),
+ Csv(entry.GrantedThrough)
+ }));
+ }
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Writes one CSV file per user to the specified directory.
+ /// File names: audit_{email}_{date}.csv
+ ///
+ public async Task WriteAsync(
+ IReadOnlyList allEntries,
+ string directoryPath,
+ CancellationToken ct)
+ {
+ Directory.CreateDirectory(directoryPath);
+ var dateStr = DateTime.Now.ToString("yyyy-MM-dd");
+
+ // Group by user
+ var byUser = allEntries.GroupBy(e => e.UserLogin);
+
+ foreach (var group in byUser)
+ {
+ ct.ThrowIfCancellationRequested();
+
+ var userLogin = group.Key;
+ var displayName = group.First().UserDisplayName;
+ var entries = group.ToList();
+
+ // Sanitize email for filename (replace @ and other invalid chars)
+ var safeLogin = SanitizeFileName(userLogin);
+ var fileName = $"audit_{safeLogin}_{dateStr}.csv";
+ var filePath = Path.Combine(directoryPath, fileName);
+
+ var csv = BuildCsv(displayName, userLogin, entries);
+ await File.WriteAllTextAsync(filePath, csv,
+ new UTF8Encoding(encoderShouldEmitUTF8Identifier: true), ct);
+ }
+ }
+
+ ///
+ /// Writes all entries to a single CSV file (alternative for single-file export).
+ /// Used when the ViewModel export command picks a single file path.
+ ///
+ public async Task WriteSingleFileAsync(
+ IReadOnlyList entries,
+ string filePath,
+ CancellationToken ct)
+ {
+ var sb = new StringBuilder();
+ var fullHeader = "\"User\",\"User Login\"," + DataHeader;
+
+ // Summary
+ var users = entries.Select(e => e.UserLogin).Distinct().ToList();
+ sb.AppendLine($"\"User Access Audit Report\"");
+ sb.AppendLine($"\"Users Audited\",\"{users.Count}\"");
+ sb.AppendLine($"\"Total Accesses\",\"{entries.Count}\"");
+ sb.AppendLine($"\"Generated\",\"{DateTime.Now:yyyy-MM-dd HH:mm:ss}\"");
+ sb.AppendLine();
+
+ sb.AppendLine(fullHeader);
+ foreach (var entry in entries)
+ {
+ sb.AppendLine(string.Join(",", new[]
+ {
+ Csv(entry.UserDisplayName),
+ Csv(entry.UserLogin),
+ Csv(entry.SiteTitle),
+ Csv(entry.ObjectType),
+ Csv(entry.ObjectTitle),
+ Csv(entry.ObjectUrl),
+ Csv(entry.PermissionLevel),
+ Csv(entry.AccessType.ToString()),
+ Csv(entry.GrantedThrough)
+ }));
+ }
+
+ await File.WriteAllTextAsync(filePath, sb.ToString(),
+ new UTF8Encoding(encoderShouldEmitUTF8Identifier: true), ct);
+ }
+
+ /// RFC 4180 CSV field escaping: wrap in double quotes, double internal quotes.
+ private static string Csv(string value)
+ {
+ if (string.IsNullOrEmpty(value)) return "\"\"";
+ return $"\"{value.Replace("\"", "\"\"")}\"";
+ }
+
+ private static string SanitizeFileName(string name)
+ {
+ var invalid = Path.GetInvalidFileNameChars();
+ var sb = new StringBuilder(name.Length);
+ foreach (var c in name)
+ sb.Append(invalid.Contains(c) ? '_' : c);
+ return sb.ToString();
+ }
+}