diff --git a/Security2/FileSystem/FileSystemAccessRule2 Class/FileSystemAccessRule2.AddFileSystemAccessRules.cs b/Security2/FileSystem/FileSystemAccessRule2 Class/FileSystemAccessRule2.AddFileSystemAccessRules.cs index cd8f067..d5ba957 100644 --- a/Security2/FileSystem/FileSystemAccessRule2 Class/FileSystemAccessRule2.AddFileSystemAccessRules.cs +++ b/Security2/FileSystem/FileSystemAccessRule2 Class/FileSystemAccessRule2.AddFileSystemAccessRules.cs @@ -87,17 +87,27 @@ namespace Security2 return ace; } + /// + /// Adds one access entry per account to the item at the path, and returns them in the order of + /// . Before 5.0.0, this overload was an iterator (a yield return in the loop + /// below), so nothing happened until the caller enumerated the result; called as a statement, or assigned to + /// a variable that is never read, it changed nothing. It now builds the list and writes the entries before + /// returning, like its FileSystemInfo and FileSystemSecurity2 siblings; an exception that adding an entry + /// raises is now thrown by this call, not by the later enumeration. + /// public static IEnumerable AddFileSystemAccessRule(string path, List accounts, FileSystemRights2 rights, AccessControlType type, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags) { if (type == AccessControlType.Allow) rights = rights | FileSystemRights2.Synchronize; + var aces = new List(); + if (File.Exists(path)) { var item = new FileInfo(path); foreach (var account in accounts) { - yield return AddFileSystemAccessRule(item, account, rights, type, inheritanceFlags, propagationFlags); + aces.Add(AddFileSystemAccessRule(item, account, rights, type, inheritanceFlags, propagationFlags)); } } else @@ -105,9 +115,11 @@ namespace Security2 var item = new DirectoryInfo(path); foreach (var account in accounts) { - yield return AddFileSystemAccessRule(item, account, rights, type, inheritanceFlags, propagationFlags); + aces.Add(AddFileSystemAccessRule(item, account, rights, type, inheritanceFlags, propagationFlags)); } } + + return aces; } public static void AddFileSystemAccessRule(FileSystemAccessRule2 rule) diff --git a/Security2/FileSystem/FileSystemAuditRule2 Class/FileSystemAuditRule2.AddFileSystemAuditRules.cs b/Security2/FileSystem/FileSystemAuditRule2 Class/FileSystemAuditRule2.AddFileSystemAuditRules.cs index 59e1b9b..5ed1c8a 100644 --- a/Security2/FileSystem/FileSystemAuditRule2 Class/FileSystemAuditRule2.AddFileSystemAuditRules.cs +++ b/Security2/FileSystem/FileSystemAuditRule2 Class/FileSystemAuditRule2.AddFileSystemAuditRules.cs @@ -78,14 +78,24 @@ namespace Security2 return aces; } + /// + /// Adds one audit entry per account to the item at the path, and returns them in the order of + /// . Before 5.0.0, this overload was an iterator (a yield return in the loop + /// below), so nothing happened until the caller enumerated the result; called as a statement, or assigned to + /// a variable that is never read, it changed nothing. It now builds the list and writes the entries before + /// returning, like its FileSystemInfo and FileSystemSecurity2 siblings; an exception that adding an entry + /// raises is now thrown by this call, not by the later enumeration. + /// public static IEnumerable AddFileSystemAuditRule(string path, List accounts, FileSystemRights2 rights, AuditFlags type, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags) { + var aces = new List(); + if (File.Exists(path)) { var item = new FileInfo(path); foreach (var account in accounts) { - yield return AddFileSystemAuditRule(item, account, rights, type, inheritanceFlags, propagationFlags); + aces.Add(AddFileSystemAuditRule(item, account, rights, type, inheritanceFlags, propagationFlags)); } } else @@ -93,9 +103,11 @@ namespace Security2 var item = new DirectoryInfo(path); foreach (var account in accounts) { - yield return AddFileSystemAuditRule(item, account, rights, type, inheritanceFlags, propagationFlags); + aces.Add(AddFileSystemAuditRule(item, account, rights, type, inheritanceFlags, propagationFlags)); } } + + return aces; } public static void AddFileSystemAuditRule(FileSystemAuditRule2 rule) diff --git a/Tests/ObjectApis.Tests.ps1 b/Tests/ObjectApis.Tests.ps1 index e2fc353..76f65ce 100644 --- a/Tests/ObjectApis.Tests.ps1 +++ b/Tests/ObjectApis.Tests.ps1 @@ -395,9 +395,11 @@ Describe 'Access rule helpers that take a path' { $entries[0].FileSystemRights | Should -Be ([System.Security.AccessControl.FileSystemRights] 'ReadData, Synchronize') } - # The overload that takes a path returns an iterator, so the caller must enumerate the result to write the entries. - # The overloads that take an item write them at once; this test doesn't pin the difference. - It 'Should add the entries of several accounts to a by its path when the result is enumerated' -ForEach @( + # Before 5.0.0, the overload that takes a path was an iterator (yield return) that changed nothing until the + # caller enumerated the result, so a call as a statement, or an assignment that the caller never reads, wrote + # nothing; the overloads that take an item or a FileSystemSecurity2 were never lazy. It now writes at once, like + # its siblings, so the entries exist before $pending is ever touched. + It 'Should add the entries of several accounts to a by its path at once, before the result is ever enumerated' -ForEach @( @{ Kind = 'file'; Directory = $false } @{ Kind = 'folder'; Directory = $true } ) { @@ -408,9 +410,13 @@ Describe 'Access rule helpers that take a path' { $path, $accounts, [Security2.FileSystemRights2]::ReadData, $allow, $noInheritance, $noPropagation ) - @($pending) | Should -HaveCount 2 @(Get-ExplicitEntries -Path $path) | Should -HaveCount 1 @(Get-ExplicitEntries -Path $path -Account 'S-1-5-32-545') | Should -HaveCount 1 + + $added = @($pending) + $added | Should -HaveCount 2 + $added[0].Account.Sid | Should -BeExactly $identity.Sid + $added[1].Account.Sid | Should -BeExactly $users.Sid } It 'Should add the deny entries of several accounts without Synchronize when the result is enumerated' { @@ -589,7 +595,10 @@ Describe 'Audit rule helpers that take a path' -Skip:(-not $holdsSecurityPrivile $found[0].FullName | Should -BeExactly $path } - It 'Should add the entries of several accounts to a by its path when the result is enumerated, and remove them again' -ForEach @( + # Before 5.0.0, the overload that takes a path was an iterator (yield return) that changed nothing until the + # caller enumerated the result; it now writes at once, like its siblings, so the entries exist before $pending is + # ever touched. This overload needs the Security privilege like its neighbors, so the whole Describe skips without it. + It 'Should add the entries of several accounts to a by its path at once, before the result is ever enumerated, and remove them again' -ForEach @( @{ Kind = 'file'; Directory = $false } @{ Kind = 'folder'; Directory = $true } ) { @@ -602,9 +611,13 @@ Describe 'Audit rule helpers that take a path' -Skip:(-not $holdsSecurityPrivile $path, $accounts, [Security2.FileSystemRights2]::Delete, $success, $noInheritance, $noPropagation ) - @($pending) | Should -HaveCount 2 @(Get-AuditEntries -Path $path) | Should -HaveCount 1 @(Get-AuditEntries -Path $path -Account 'S-1-5-32-545') | Should -HaveCount 1 + $added = @($pending) + $added | Should -HaveCount 2 + $added[0].Account.Sid | Should -BeExactly $identity.Sid + $added[1].Account.Sid | Should -BeExactly $users.Sid + [Security2.FileSystemAuditRule2]::RemoveFileSystemAuditRule( $path, $identity, [Security2.FileSystemRights2]::Delete, $success, $noInheritance, $noPropagation, $true )