From 2217a393be6dad14813cdcd7bb35bf765037875f Mon Sep 17 00:00:00 2001 From: Meik Date: Thu, 18 Jun 2026 21:32:13 +0200 Subject: [PATCH] Add automatic NTFS traverse ensure --- LiamNtfs/C4IT.LIAM.Ntfs.cs | 71 +++++ LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs | 257 ++++++++++++------ LiamWorkflowActivities/LiamWorkflowRuntime.cs | 162 +++++++---- 3 files changed, 353 insertions(+), 137 deletions(-) diff --git a/LiamNtfs/C4IT.LIAM.Ntfs.cs b/LiamNtfs/C4IT.LIAM.Ntfs.cs index 6b6a410..0d44b15 100644 --- a/LiamNtfs/C4IT.LIAM.Ntfs.cs +++ b/LiamNtfs/C4IT.LIAM.Ntfs.cs @@ -1178,6 +1178,77 @@ namespace C4IT.LIAM return Task.FromResult(resultToken); } + public Task EnsureTraverseGroupsAsync( + string folderPath, + IDictionary customTags = null, + bool whatIf = false) + { + var classification = ClassifyPath(folderPath); + if (!IsSupportedPermissionManagedPathKind( + classification, + eNtfsPathKind.Folder, + eNtfsPathKind.ClassicShare, + eNtfsPathKind.DfsLink)) + { + return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) + { + resultErrorId = 30008, + resultMessage = $"NTFS traverse ensure is only supported for folder and share paths. DFS namespaces and server roots are skipped: {folderPath}" + }); + } + + string matchingConfigurationKey; + string matchingRule; + if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule)) + { + return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) + { + resultErrorId = 30008, + resultMessage = $"NTFS traverse ensure skipped for '{folderPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'." + }); + } + + if (!IsTraversePermissionManagedPath(folderPath)) + { + return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) + { + resultErrorId = 30008, + resultMessage = $"NTFS traverse ensure skipped for '{folderPath}' because the path is not managed by the traverse configuration." + }); + } + + var parentPath = GetEngineParentPath(classification, folderPath); + var engine = CreateFilesystemEngine( + folderPath, + parentPath, + customTags, + null, + null, + null); + engine.WhatIf = whatIf; + + return Task.FromResult(engine.ensureTraversePermissionsOnly()); + } + + private static string GetEngineParentPath(cNtfsPathClassification classification, string folderPath) + { + if (classification != null && !string.IsNullOrWhiteSpace(classification.ParentPath)) + return classification.ParentPath; + + if (classification != null && !string.IsNullOrWhiteSpace(classification.ParentBoundaryPath)) + return classification.ParentBoundaryPath; + + try + { + var parentPath = Directory.GetParent(folderPath)?.FullName; + return string.IsNullOrWhiteSpace(parentPath) ? folderPath : parentPath; + } + catch + { + return folderPath; + } + } + private DataArea_FileSystem CreateFilesystemEngine( string folderPath, string parentFolderPath, diff --git a/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs b/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs index 6152f54..7aaecb3 100644 --- a/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs +++ b/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs @@ -324,11 +324,11 @@ namespace C4IT_IAM_SET if (string.IsNullOrWhiteSpace(boundaryPath)) return resultToken; - var targetParent = new DirectoryInfo(newFolderPath).Parent; - if (targetParent == null) + var targetPath = NormalizeDirectoryPath(newFolderPath); + if (string.IsNullOrWhiteSpace(targetPath)) { resultToken.resultErrorId = 30009; - resultToken.resultMessage = $"Traverse boundary '{traverseBoundaryPath}' cannot be validated because '{newFolderPath}' has no parent directory."; + resultToken.resultMessage = $"Traverse boundary '{traverseBoundaryPath}' cannot be validated because the target path is empty."; return resultToken; } @@ -339,10 +339,10 @@ namespace C4IT_IAM_SET return resultToken; } - if (!IsSameOrAncestorPath(boundaryPath, targetParent.FullName)) + if (!IsSameOrAncestorPath(boundaryPath, targetPath)) { resultToken.resultErrorId = 30009; - resultToken.resultMessage = $"Traverse boundary '{traverseBoundaryPath}' is not a parent path of '{newFolderPath}'."; + resultToken.resultMessage = $"Traverse boundary '{traverseBoundaryPath}' is not the target path or a parent path of '{newFolderPath}'."; } return resultToken; @@ -388,13 +388,6 @@ namespace C4IT_IAM_SET if (ensureTraverseGroups) { - if (WhatIf) - { - resultToken.warnings.Add("Traverse group preview is not supported in WhatIf mode for automatic DataArea ensure."); - resultToken.resultMessage = "Gruppen- und ACL-Vorschau erfolgreich erstellt"; - return resultToken; - } - var traverseResult = SetTraversePermissions(); if (traverseResult != null) { @@ -430,7 +423,105 @@ namespace C4IT_IAM_SET } } - private ResultToken SetTraversePermissions() + public ResultToken ensureTraversePermissionsOnly() + { + LogMethodBegin(MethodBase.GetCurrentMethod()); + + try + { + var resultToken = checkRequiredVariablesForEnsure(); + if (resultToken.resultErrorId != 0) + return resultToken; + + if (Connection != null) + Connection.Dispose(); + + using (Connection = new cNetworkConnection(baseFolder, username, new NetworkCredential("", password).Password)) + { + if (!Directory.Exists(newFolderPath)) + { + resultToken.resultErrorId = 30203; + resultToken.resultMessage = "Verzeichnis existiert nicht"; + return resultToken; + } + + var parentDirectory = Directory.GetParent(newFolderPath); + if (string.IsNullOrWhiteSpace(newFolderParent)) + newFolderParent = parentDirectory?.FullName; + + InitializeFolderContext(); + + var traverseBoundaryResult = ValidateTraverseBoundaryForCurrentFolder(); + if (traverseBoundaryResult.resultErrorId != 0) + return traverseBoundaryResult; + + EnsureExistingPermissionGroupsForTraverse(resultToken); + resultToken = MergeResultTokens(resultToken, SetTraversePermissions(true)); + if (resultToken.resultErrorId == 0) + { + resultToken.resultMessage = WhatIf + ? "Traverse-Gruppen- und ACL-Vorschau erfolgreich erstellt" + : "Traverse-Gruppen und ACLs erfolgreich sichergestellt"; + } + + return resultToken; + } + } + catch (Exception E) + { + cLogManager.DefaultLogger.LogException(E); + throw; + } + finally + { + LogMethodEnd(MethodBase.GetCurrentMethod()); + } + } + + private void EnsureExistingPermissionGroupsForTraverse(ResultToken resultToken) + { + newSecurityGroups.IAM_SecurityGroups.Clear(); + newSecurityGroups.GenerateNewSecurityGroups(baseFolder, + newDataArea.IAM_Folders[0].technicalName, + groupPrefix, + groupOUPath, + groupPermissionStrategy, + groupTraverseTag, + groupReadTag, + groupWriteTag, + groupOwnerTag, + groupDLTag, + groupGTag, + groupCustomTags, + templates, + ReadACLPermission, + WriteACLPermission, + OwnerACLPermission, + 0, + 0, + groupNameSanitizeReplacement, + preserveAdGroupNameCase); + + var existingGroups = new List(); + foreach (var securityGroup in newSecurityGroups.IAM_SecurityGroups) + { + var existingGroup = newSecurityGroups.PreviewADGroup(groupOUPath, securityGroup, newDataArea.IAM_Folders[0].technicalName); + if (existingGroup == null || string.IsNullOrWhiteSpace(securityGroup.UID)) + { + if (securityGroup.Scope == GroupScope.Global) + resultToken.warnings.Add($"Traverse-only: LIAM-Berechtigungsgruppe '{securityGroup.Name}' fehlt und wird nicht angelegt."); + continue; + } + + resultToken.reusedGroups.Add(securityGroup.Name); + existingGroups.Add(securityGroup); + } + + newSecurityGroups.IAM_SecurityGroups.Clear(); + newSecurityGroups.IAM_SecurityGroups.AddRange(existingGroups); + } + + private ResultToken SetTraversePermissions(bool includeCurrentFolder = false) { LogMethodBegin(MethodBase.GetCurrentMethod()); @@ -467,13 +558,13 @@ namespace C4IT_IAM_SET DirectoryInfo newDir = new DirectoryInfo(newDataArea.IAM_Folders[0].technicalName); DefaultLogger.LogEntry(LogLevels.Debug, $"Neues Verzeichnis: {newDir.FullName}"); - DirectoryInfo parent = newDir.Parent; + DirectoryInfo parent = includeCurrentFolder ? newDir : newDir.Parent; if (parent == null) { - DefaultLogger.LogEntry(LogLevels.Error, "Parent-Verzeichnis ist null."); + DefaultLogger.LogEntry(LogLevels.Error, "Traverse-Startverzeichnis ist null."); return resultToken; } - DefaultLogger.LogEntry(LogLevels.Debug, $"Parent-Verzeichnis: {parent.FullName}"); + DefaultLogger.LogEntry(LogLevels.Debug, $"Traverse-Startverzeichnis: {parent.FullName}"); var lvl = DataArea.GetRelativePath(parent.FullName, baseFolder).Count(n => n == Path.DirectorySeparatorChar); DefaultLogger.LogEntry(LogLevels.Debug, $"Ebene (lvl): {lvl}"); @@ -519,6 +610,7 @@ namespace C4IT_IAM_SET } GroupPrincipal traverseGroup = null; + string traverseGroupName = null; // Überprüfen, ob createTraverseGroupLvl initialisiert ist DefaultLogger.LogEntry(LogLevels.Debug, $"createTraverseGroupLvl: {createTraverseGroupLvl}"); @@ -583,6 +675,7 @@ namespace C4IT_IAM_SET } GroupPrincipal parentTraverseGroup = null; + string parentTraverseGroupName = null; var parentTraverseAclExists = false; string relativePathRaw = DataArea.GetRelativePath(parent.FullName, baseFolder).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); relativePathRaw = relativePathRaw.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); @@ -684,11 +777,15 @@ namespace C4IT_IAM_SET break; } + if (parentTraverseGroup != null) + parentTraverseGroupName = parentTraverseGroup.Name; + if (parentTraverseGroup == null && hasTraverseWildcard && !forceStrictAdGroupNames) { parentTraverseGroup = FindTraverseGroupByWildcard(domainContext, traverseRegex); if (parentTraverseGroup != null) { + parentTraverseGroupName = parentTraverseGroup.Name; resultToken.reusedGroups.Add(parentTraverseGroup.Name); resultToken.ensuredTraverseGroups.Add(parentTraverseGroup.Name); DefaultLogger.LogEntry(LogLevels.Debug, $"Vorhandene Traverse-Gruppe per Wildcard wiederverwendet: {parentTraverseGroup.Name}"); @@ -704,6 +801,7 @@ namespace C4IT_IAM_SET if (parentTraverseGroup == null) continue; + parentTraverseGroupName = parentTraverseGroup.Name; resultToken.reusedGroups.Add(candidateName); resultToken.ensuredTraverseGroups.Add(candidateName); DefaultLogger.LogEntry(LogLevels.Debug, $"Vorhandene Traverse-Gruppe wiederverwendet: {candidateName}"); @@ -751,60 +849,56 @@ namespace C4IT_IAM_SET continue; } - if (parent.Parent != null) + DefaultLogger.LogEntry(LogLevels.Debug, "Erstelle AD-Gruppe."); + if (WhatIf) { - DefaultLogger.LogEntry(LogLevels.Debug, "Parent.Parent ist nicht null. Erstelle AD-Gruppe."); - if (WhatIf) - { - resultToken.createdGroups.Add(newTraverseGroup.Name); - resultToken.ensuredTraverseGroups.Add(newTraverseGroup.Name); - resultToken.warnings.Add($"Traverse-Gruppe würde angelegt werden: {newTraverseGroup.Name}"); - resultToken.addedAclEntries.Add(newTraverseGroup.Name); - parentTraverseAclExists = true; - } - else - { - try - { - newSecurityGroups.CreateADGroup(groupOUPath, newTraverseGroup, null); - DefaultLogger.LogEntry(LogLevels.Debug, $"AD-Gruppe erstellt: {newTraverseGroup.Name}"); - resultToken.createdGroups.Add(newTraverseGroup.Name); - resultToken.ensuredTraverseGroups.Add(newTraverseGroup.Name); - } - catch (Exception ex) - { - DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Erstellen der AD-Gruppe: {ex.Message}"); - continue; - } - - parentTraverseGroup = GroupPrincipal.FindByIdentity(domainContext, newTraverseGroup.Name); - if (parentTraverseGroup == null) - { - DefaultLogger.LogEntry(LogLevels.Error, $"parentTraverseGroup konnte nach Erstellung der Gruppe nicht gefunden werden: {newTraverseGroup.Name}"); - continue; - } - - try - { - var accesscontrol = parent.GetAccessControl(); - accesscontrol.AddAccessRule(new FileSystemAccessRule(parentTraverseGroup.Sid, - FileSystemRights.Read, InheritanceFlags.None, PropagationFlags.None, - AccessControlType.Allow)); - DefaultLogger.LogEntry(LogLevels.Debug, $"Setze Traverse-ACL auf: {parent.FullName} für {parentTraverseGroup.DistinguishedName}"); - parent.SetAccessControl(accesscontrol); - resultToken.addedAclEntries.Add(parentTraverseGroup.Name); - parentTraverseAclExists = true; - } - catch (Exception ex) - { - DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Setzen der ACL: {ex.Message}"); - continue; - } - } + parentTraverseGroupName = newTraverseGroup.Name; + resultToken.createdGroups.Add(newTraverseGroup.Name); + resultToken.ensuredTraverseGroups.Add(newTraverseGroup.Name); + resultToken.warnings.Add($"Traverse-Gruppe würde angelegt werden: {newTraverseGroup.Name}"); + resultToken.addedAclEntries.Add(newTraverseGroup.Name); + parentTraverseAclExists = true; } else { - DefaultLogger.LogEntry(LogLevels.Debug, "Parent.Parent ist null. Traverse-ACL kann nicht gesetzt werden."); + try + { + newSecurityGroups.CreateADGroup(groupOUPath, newTraverseGroup, null); + DefaultLogger.LogEntry(LogLevels.Debug, $"AD-Gruppe erstellt: {newTraverseGroup.Name}"); + resultToken.createdGroups.Add(newTraverseGroup.Name); + resultToken.ensuredTraverseGroups.Add(newTraverseGroup.Name); + } + catch (Exception ex) + { + DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Erstellen der AD-Gruppe: {ex.Message}"); + continue; + } + + parentTraverseGroup = GroupPrincipal.FindByIdentity(domainContext, newTraverseGroup.Name); + if (parentTraverseGroup == null) + { + DefaultLogger.LogEntry(LogLevels.Error, $"parentTraverseGroup konnte nach Erstellung der Gruppe nicht gefunden werden: {newTraverseGroup.Name}"); + continue; + } + + parentTraverseGroupName = parentTraverseGroup.Name; + + try + { + var accesscontrol = parent.GetAccessControl(); + accesscontrol.AddAccessRule(new FileSystemAccessRule(parentTraverseGroup.Sid, + FileSystemRights.Read, InheritanceFlags.None, PropagationFlags.None, + AccessControlType.Allow)); + DefaultLogger.LogEntry(LogLevels.Debug, $"Setze Traverse-ACL auf: {parent.FullName} für {parentTraverseGroup.DistinguishedName}"); + parent.SetAccessControl(accesscontrol); + resultToken.addedAclEntries.Add(parentTraverseGroup.Name); + parentTraverseAclExists = true; + } + catch (Exception ex) + { + DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Setzen der ACL: {ex.Message}"); + continue; + } } } } @@ -843,8 +937,9 @@ namespace C4IT_IAM_SET } } - if (parentTraverseGroup != null) + if (parentTraverseGroup != null || !string.IsNullOrWhiteSpace(parentTraverseGroupName)) { + var displayTraverseGroupName = parentTraverseGroup?.Name ?? parentTraverseGroupName; if (!processedNearestTraverseParent) { DefaultLogger.LogEntry(LogLevels.Debug, "Verarbeite SecurityGroups bei oberster Ebene."); @@ -860,7 +955,7 @@ namespace C4IT_IAM_SET if (WhatIf) { - resultToken.warnings.Add($"Traverse-Gruppe '{parentTraverseGroup.Name}' würde Mitglied '{currentSecGroup.Name}' erhalten."); + resultToken.warnings.Add($"Traverse-Gruppe '{displayTraverseGroupName}' würde Mitglied '{currentSecGroup.Name}' erhalten."); continue; } @@ -868,25 +963,23 @@ namespace C4IT_IAM_SET continue; } traverseGroup = parentTraverseGroup; + traverseGroupName = displayTraverseGroupName; processedNearestTraverseParent = true; } else { - if (traverseGroup != null && parentTraverseGroup != null) + if (!string.IsNullOrWhiteSpace(traverseGroupName)) { try { - if (!parentTraverseGroup.Members.Contains(traverseGroup)) + if (WhatIf) { - if (WhatIf) - { - resultToken.warnings.Add($"Traverse-Gruppe '{parentTraverseGroup.Name}' würde verschachtelte Gruppe '{traverseGroup.Name}' erhalten."); - } - else - { - if (!TryEnsureNestedTraverseGroupMembershipWithRetry(parentTraverseGroup, traverseGroup)) - continue; - } + resultToken.warnings.Add($"Traverse-Gruppe '{displayTraverseGroupName}' würde verschachtelte Gruppe '{traverseGroupName}' erhalten."); + } + else if (traverseGroup != null && parentTraverseGroup != null && !parentTraverseGroup.Members.Contains(traverseGroup)) + { + if (!TryEnsureNestedTraverseGroupMembershipWithRetry(parentTraverseGroup, traverseGroup)) + continue; } } catch (Exception ex) @@ -898,7 +991,7 @@ namespace C4IT_IAM_SET } try { - if (!WhatIf) + if (!WhatIf && parentTraverseGroup != null) { parentTraverseGroup.Save(); DefaultLogger.LogEntry(LogLevels.Debug, $"parentTraverseGroup gespeichert: {parentTraverseGroup.Name}"); @@ -914,8 +1007,8 @@ namespace C4IT_IAM_SET DefaultLogger.LogEntry(LogLevels.Debug, "parentTraverseGroup ist null."); } - if (parentTraverseGroup != null && !resultToken.ensuredTraverseGroups.Contains(parentTraverseGroup.Name)) - resultToken.ensuredTraverseGroups.Add(parentTraverseGroup.Name); + if (!string.IsNullOrWhiteSpace(parentTraverseGroupName) && !resultToken.ensuredTraverseGroups.Contains(parentTraverseGroupName)) + resultToken.ensuredTraverseGroups.Add(parentTraverseGroupName); if (IsTraverseBoundaryPath(parent.FullName)) break; diff --git a/LiamWorkflowActivities/LiamWorkflowRuntime.cs b/LiamWorkflowActivities/LiamWorkflowRuntime.cs index 99645e8..762779e 100644 --- a/LiamWorkflowActivities/LiamWorkflowRuntime.cs +++ b/LiamWorkflowActivities/LiamWorkflowRuntime.cs @@ -421,60 +421,112 @@ namespace LiamWorkflowActivities var allowFolderEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsPermissionGroups"); var allowSharePathEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsPermissionGroupsForShares"); - if (!allowFolderEnsure && !allowSharePathEnsure) + var allowTraverseEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsTraverseGroups"); + if (!allowFolderEnsure && !allowSharePathEnsure && !allowTraverseEnsure) return true; - foreach (var ntfsArea in dataAreas - .Where(dataArea => - allowFolderEnsure && dataArea is cLiamNtfsFolder - || allowSharePathEnsure && dataArea is cLiamNtfsShare) - .Cast()) + if (allowFolderEnsure || allowSharePathEnsure) { - var folderPath = ntfsArea.TechnicalName; - if (string.IsNullOrWhiteSpace(folderPath)) - continue; - - if (!Directory.Exists(folderPath)) + foreach (var ntfsArea in dataAreas + .Where(dataArea => + allowFolderEnsure && dataArea is cLiamNtfsFolder + || allowSharePathEnsure && dataArea is cLiamNtfsShare) + .Cast()) { - LogEntry($"Skipping automatic NTFS permission group ensure for '{folderPath}' because the directory does not exist.", LogLevels.Warning); - continue; - } + var folderPath = ntfsArea.TechnicalName; + if (string.IsNullOrWhiteSpace(folderPath)) + continue; - var ensureResult = await ntfsProvider.EnsureMissingPermissionGroupsAsync( - folderPath, - null, - null, - null, - null, - allowSharePathEnsure, - ntfsArea is cLiamNtfsFolder || ntfsArea is cLiamNtfsShare, - simulateOnly); - if (ensureResult == null) + if (!Directory.Exists(folderPath)) + { + LogEntry($"Skipping automatic NTFS permission group ensure for '{folderPath}' because the directory does not exist.", LogLevels.Warning); + continue; + } + + var ensureResult = await ntfsProvider.EnsureMissingPermissionGroupsAsync( + folderPath, + null, + null, + null, + null, + allowSharePathEnsure, + !allowTraverseEnsure && (ntfsArea is cLiamNtfsFolder || ntfsArea is cLiamNtfsShare), + simulateOnly); + if (ensureResult == null) + { + var providerMessage = ntfsProvider.GetLastErrorMessage() ?? "Provider returned no result."; + LogEntry( + $"Automatic NTFS permission group ensure skipped for '{folderPath}' because the provider returned no result. ProviderMessage='{providerMessage}'", + LogLevels.Warning); + continue; + } + + if (ensureResult.resultErrorId != 0) + { + LogEntry( + $"Automatic NTFS permission group ensure skipped for '{folderPath}' after resultErrorId={ensureResult.resultErrorId}. ResultMessage='{ensureResult.resultMessage ?? string.Empty}'", + LogLevels.Warning); + continue; + } + + if (simulateOnly) + { + LogAutomaticNtfsEnsurePreviewDebug(folderPath, ensureResult, "permission group ensure"); + result.AutomaticEnsurePreview.Add(MapAutomaticEnsurePreview(folderPath, ensureResult)); + continue; + } + + LogAutomaticNtfsEnsureDebug(folderPath, ensureResult, "permission group ensure"); + await ntfsArea.ResolvePermissionGroupsAsync(folderPath); + } + } + + if (allowTraverseEnsure) + { + foreach (var ntfsArea in dataAreas + .Where(dataArea => dataArea is cLiamNtfsFolder || dataArea is cLiamNtfsShare) + .Cast()) { - var providerMessage = ntfsProvider.GetLastErrorMessage() ?? "Provider returned no result."; - LogEntry( - $"Automatic NTFS permission group ensure skipped for '{folderPath}' because the provider returned no result. ProviderMessage='{providerMessage}'", - LogLevels.Warning); - continue; - } + var folderPath = ntfsArea.TechnicalName; + if (string.IsNullOrWhiteSpace(folderPath)) + continue; - if (ensureResult.resultErrorId != 0) - { - LogEntry( - $"Automatic NTFS permission group ensure skipped for '{folderPath}' after resultErrorId={ensureResult.resultErrorId}. ResultMessage='{ensureResult.resultMessage ?? string.Empty}'", - LogLevels.Warning); - continue; - } + if (!Directory.Exists(folderPath)) + { + LogEntry($"Skipping automatic NTFS traverse group ensure for '{folderPath}' because the directory does not exist.", LogLevels.Warning); + continue; + } - if (simulateOnly) - { - LogAutomaticNtfsEnsurePreviewDebug(folderPath, ensureResult); - result.AutomaticEnsurePreview.Add(MapAutomaticEnsurePreview(folderPath, ensureResult)); - continue; - } + var ensureResult = await ntfsProvider.EnsureTraverseGroupsAsync( + folderPath, + null, + simulateOnly); + if (ensureResult == null) + { + var providerMessage = ntfsProvider.GetLastErrorMessage() ?? "Provider returned no result."; + LogEntry( + $"Automatic NTFS traverse group ensure skipped for '{folderPath}' because the provider returned no result. ProviderMessage='{providerMessage}'", + LogLevels.Warning); + continue; + } - LogAutomaticNtfsEnsureDebug(folderPath, ensureResult); - await ntfsArea.ResolvePermissionGroupsAsync(folderPath); + if (ensureResult.resultErrorId != 0) + { + LogEntry( + $"Automatic NTFS traverse group ensure skipped for '{folderPath}' after resultErrorId={ensureResult.resultErrorId}. ResultMessage='{ensureResult.resultMessage ?? string.Empty}'", + LogLevels.Warning); + continue; + } + + if (simulateOnly) + { + LogAutomaticNtfsEnsurePreviewDebug(folderPath, ensureResult, "traverse group ensure"); + result.AutomaticEnsurePreview.Add(MapAutomaticEnsurePreview(folderPath, ensureResult)); + continue; + } + + LogAutomaticNtfsEnsureDebug(folderPath, ensureResult, "traverse group ensure"); + } } return true; @@ -496,13 +548,13 @@ namespace LiamWorkflowActivities }; } - private static void LogAutomaticNtfsEnsurePreviewDebug(string folderPath, ResultToken ensureResult) + private static void LogAutomaticNtfsEnsurePreviewDebug(string folderPath, ResultToken ensureResult, string operationLabel) { if (ensureResult == null) return; LogEntry( - $"Automatic NTFS permission group ensure preview finished for '{folderPath}'. " + + $"Automatic NTFS {operationLabel} preview finished for '{folderPath}'. " + $"WouldCreateGroups={ensureResult.createdGroups.Count}, " + $"WouldReuseGroups={ensureResult.reusedGroups.Count}, " + $"WouldAddAcls={ensureResult.addedAclEntries.Count}, " + @@ -513,13 +565,13 @@ namespace LiamWorkflowActivities LogLevels.Debug); } - private static void LogAutomaticNtfsEnsureDebug(string folderPath, ResultToken ensureResult) + private static void LogAutomaticNtfsEnsureDebug(string folderPath, ResultToken ensureResult, string operationLabel) { if (ensureResult == null) return; LogEntry( - $"Automatic NTFS permission group ensure finished for '{folderPath}'. " + + $"Automatic NTFS {operationLabel} finished for '{folderPath}'. " + $"CreatedGroups={ensureResult.createdGroups.Count}, " + $"ReusedGroups={ensureResult.reusedGroups.Count}, " + $"AddedAcls={ensureResult.addedAclEntries.Count}, " + @@ -532,42 +584,42 @@ namespace LiamWorkflowActivities if (ensureResult.createdGroups.Count > 0) { LogEntry( - $"Automatic NTFS permission group ensure detected missing AD groups for '{folderPath}' and created them: {string.Join(", ", ensureResult.createdGroups)}", + $"Automatic NTFS {operationLabel} detected missing AD groups for '{folderPath}' and created them: {string.Join(", ", ensureResult.createdGroups)}", LogLevels.Debug); } if (ensureResult.reusedGroups.Count > 0) { LogEntry( - $"Automatic NTFS permission group ensure reused existing AD groups for '{folderPath}': {string.Join(", ", ensureResult.reusedGroups)}", + $"Automatic NTFS {operationLabel} reused existing AD groups for '{folderPath}': {string.Join(", ", ensureResult.reusedGroups)}", LogLevels.Debug); } if (ensureResult.addedAclEntries.Count > 0) { LogEntry( - $"Automatic NTFS permission group ensure added missing ACL entries for '{folderPath}': {string.Join(", ", ensureResult.addedAclEntries)}", + $"Automatic NTFS {operationLabel} added missing ACL entries for '{folderPath}': {string.Join(", ", ensureResult.addedAclEntries)}", LogLevels.Debug); } if (ensureResult.skippedAclEntries.Count > 0) { LogEntry( - $"Automatic NTFS permission group ensure kept existing ACL entries for '{folderPath}': {string.Join(", ", ensureResult.skippedAclEntries)}", + $"Automatic NTFS {operationLabel} kept existing ACL entries for '{folderPath}': {string.Join(", ", ensureResult.skippedAclEntries)}", LogLevels.Debug); } if (ensureResult.ensuredTraverseGroups.Count > 0) { LogEntry( - $"Automatic NTFS permission group ensure touched traverse groups for '{folderPath}': {string.Join(", ", ensureResult.ensuredTraverseGroups)}", + $"Automatic NTFS {operationLabel} touched traverse groups for '{folderPath}': {string.Join(", ", ensureResult.ensuredTraverseGroups)}", LogLevels.Debug); } if (ensureResult.warnings.Count > 0) { LogEntry( - $"Automatic NTFS permission group ensure produced warnings for '{folderPath}': {string.Join(" | ", ensureResult.warnings)}", + $"Automatic NTFS {operationLabel} produced warnings for '{folderPath}': {string.Join(" | ", ensureResult.warnings)}", LogLevels.Debug); } }