using System.IO; using System.Text; using SharepointToolbox.Core.Models; namespace SharepointToolbox.Services.Export; /// /// Exports permission entries to CSV format. /// Ports PowerShell Merge-PermissionRows + Export-Csv functionality. /// public class CsvExportService { private const string Header = "\"Object\",\"Title\",\"URL\",\"HasUniquePermissions\",\"Users\",\"UserLogins\",\"Type\",\"Permissions\",\"GrantedThrough\""; /// /// Builds a CSV string from the supplied permission entries. /// Merges rows with identical (Users, PermissionLevels, GrantedThrough) by pipe-joining URLs and Titles. /// public string BuildCsv(IReadOnlyList entries) { var sb = new StringBuilder(); sb.AppendLine(Header); // Merge: group by (Users, PermissionLevels, GrantedThrough) — port of PS Merge-PermissionRows var merged = entries .GroupBy(e => (e.Users, e.PermissionLevels, e.GrantedThrough)) .Select(g => new { ObjectType = g.First().ObjectType, Title = string.Join(" | ", g.Select(e => e.Title).Distinct()), Url = string.Join(" | ", g.Select(e => e.Url).Distinct()), HasUnique = g.First().HasUniquePermissions, Users = g.Key.Users, UserLogins = g.First().UserLogins, PrincipalType = g.First().PrincipalType, Permissions = g.Key.PermissionLevels, GrantedThrough = g.Key.GrantedThrough }); foreach (var row in merged) sb.AppendLine(string.Join(",", new[] { Csv(row.ObjectType), Csv(row.Title), Csv(row.Url), Csv(row.HasUnique.ToString()), Csv(row.Users), Csv(row.UserLogins), Csv(row.PrincipalType), Csv(row.Permissions), Csv(row.GrantedThrough) })); return sb.ToString(); } /// /// Writes the CSV to the specified file path using UTF-8 with BOM (for Excel compatibility). /// public async Task WriteAsync(IReadOnlyList entries, string filePath, CancellationToken ct) { var csv = BuildCsv(entries); await File.WriteAllTextAsync(filePath, csv, 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("\"", "\"\"")}\""; } }