From f1f3d8f0789f830f991c927197863456f74bf0fa Mon Sep 17 00:00:00 2001 From: Raimund Andree Date: Sat, 10 Oct 2026 22:23:39 +0000 Subject: [PATCH] test(inheritance): read the SACL alone where a test asserts the audit protection flag Get-Acl -Audit reads the SACL together with the other sections. On a domain member (Windows Server 2019, 2022, and 2025 of the operating-system matrix) that read reports the SACL of an item without audit entries as protected from inheritance, where the read of the SACL alone and Get-NTFSInheritance report it as not protected (FileSystemSecurity2 documents the same difference). The test 'Disable-NTFSAuditInheritance -SecurityDescriptor should take two piped descriptors ...' asserted the on-disk flag of a fresh file with Get-Acl -Audit, and failed in all six elevated configurations of the three file servers. Diagnostic runs on 2019 and 2025 show both reads side by side. Four more assertions read the flag of an item that ends without audit entries and expect "protected"; there they could not tell a written flag from none. The five assertions now read the SACL alone through .NET, as the module does. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: AI Assistant --- Tests/ParameterSets.Audit.Tests.ps1 | 23 +++++++++++++++++- Tests/ParameterSets.Inheritance.Tests.ps1 | 29 +++++++++++++++++++---- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/Tests/ParameterSets.Audit.Tests.ps1 b/Tests/ParameterSets.Audit.Tests.ps1 index 4677afe..be9fe4f 100644 --- a/Tests/ParameterSets.Audit.Tests.ps1 +++ b/Tests/ParameterSets.Audit.Tests.ps1 @@ -41,6 +41,27 @@ BeforeAll { (Get-Acl -LiteralPath $Path).Sddl } + function Get-TestAuditAcl { + # The SACL read alone, through .NET. Get-Acl -Audit reads it together with the other sections, and on a domain + # member that reports the SACL of an item without audit entries as protected from inheritance, which is not its + # state (see Security2\FileSystem\FileSystemSecurity2.cs). Matches the helper of ParameterSets.Inheritance.Tests.ps1. + param ([Parameter(Mandatory)] [string] $Path) + + $sections = [System.Security.AccessControl.AccessControlSections]::Audit + $info = if (Test-Path -LiteralPath $Path -PathType Container) { + New-Object -TypeName 'System.IO.DirectoryInfo' -ArgumentList $Path + } + else { + New-Object -TypeName 'System.IO.FileInfo' -ArgumentList $Path + } + if ($PSVersionTable.PSEdition -eq 'Desktop') { + $info.GetAccessControl($sections) + } + else { + [System.IO.FileSystemAclExtensions]::GetAccessControl($info, $sections) + } + } + function Get-TestAccessOnlyDescriptor { # A descriptor read with the Access section only, like one that Get-NTFSSecurityDescriptor returns without # the Security privilege: the audit cmdlets have nothing to read or change and report it the same way. @@ -337,7 +358,7 @@ Describe 'Clear-NTFSAudit' { Clear-NTFSAudit -Path $child -DisableInheritance -ErrorVariable clearErrors -ErrorAction SilentlyContinue $clearErrors | Should -BeNullOrEmpty - (Get-Acl -LiteralPath $child -Audit).AreAuditRulesProtected | Should -BeTrue + (Get-TestAuditAcl -Path $child).AreAuditRulesProtected | Should -BeTrue @((Get-Acl -LiteralPath $child -Audit).GetAuditRules($true, $true, $sidType)) | Should -BeNullOrEmpty Get-TestDaclSddl -Path $child | Should -BeExactly $daclBefore } diff --git a/Tests/ParameterSets.Inheritance.Tests.ps1 b/Tests/ParameterSets.Inheritance.Tests.ps1 index bb9e073..f9bc66a 100644 --- a/Tests/ParameterSets.Inheritance.Tests.ps1 +++ b/Tests/ParameterSets.Inheritance.Tests.ps1 @@ -123,6 +123,27 @@ BeforeAll { [System.IO.FileSystemAclExtensions]::GetAccessControl($info) } } + + function Get-TestAuditAcl { + # The SACL read alone, through .NET. Get-Acl -Audit reads it together with the other sections, and on a domain + # member that reports the SACL of an item without audit entries as protected from inheritance, which is not its + # state (see Security2\FileSystem\FileSystemSecurity2.cs). Matches the helper of ParameterSets.Audit.Tests.ps1. + param ([string] $Path) + + $sections = [System.Security.AccessControl.AccessControlSections]::Audit + $info = if (Test-Path -LiteralPath $Path -PathType Container) { + New-Object -TypeName 'System.IO.DirectoryInfo' -ArgumentList $Path + } + else { + New-Object -TypeName 'System.IO.FileInfo' -ArgumentList $Path + } + if ($PSVersionTable.PSEdition -eq 'Desktop') { + $info.GetAccessControl($sections) + } + else { + [System.IO.FileSystemAclExtensions]::GetAccessControl($info, $sections) + } + } } AfterAll { @@ -276,7 +297,7 @@ Describe 'State: independent .NET oracles for inheritance transitions and descri Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop (Get-Acl -LiteralPath $file).AreAccessRulesProtected | Should -BeTrue - (Get-Acl -LiteralPath $file -Audit).AreAuditRulesProtected | Should -BeTrue + (Get-TestAuditAcl -Path $file).AreAuditRulesProtected | Should -BeTrue } It 'Set-NTFSOwner should change only the owner and leave the Access entries and the group unchanged' { @@ -469,7 +490,7 @@ Describe 'Pipeline: binding into the -Path and -SecurityDescriptor sets that no } else { $item.AuditInheritanceEnabled | Should -Be $Expected - (Get-Acl -LiteralPath $child -Audit).AreAuditRulesProtected | Should -Be (-not $Expected) + (Get-TestAuditAcl -Path $child).AreAuditRulesProtected | Should -Be (-not $Expected) } } } @@ -500,7 +521,7 @@ Describe 'Pipeline: binding into the -Path and -SecurityDescriptor sets that no } else { $descriptors[$i].SecurityDescriptor.AreAuditRulesProtected | Should -Be (-not $Expected) - (Get-Acl -LiteralPath $files[$i] -Audit).AreAuditRulesProtected | Should -Be $PreDisable + (Get-TestAuditAcl -Path $files[$i]).AreAuditRulesProtected | Should -Be $PreDisable } } @@ -508,7 +529,7 @@ Describe 'Pipeline: binding into the -Path and -SecurityDescriptor sets that no foreach ($file in $files) { if ($Scope -eq 'access') { (Get-Acl -LiteralPath $file).AreAccessRulesProtected | Should -Be (-not $Expected) } - else { (Get-Acl -LiteralPath $file -Audit).AreAuditRulesProtected | Should -Be (-not $Expected) } + else { (Get-TestAuditAcl -Path $file).AreAuditRulesProtected | Should -Be (-not $Expected) } } # A direct array binds both descriptors within one ProcessRecord call, unlike the pipeline above, which