test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
<#
Closes the parameter-set gaps that the judged test matrix ( judged-audit . csv / judged-audit -notes . md ) found for
the audit cmdlets : pipeline binding by value and by property name , continuation past a failing path or
descriptor , and state or output assertions beyond -PassThru , with the module built in NTFSSecurity \ bin \ Release
on files in a sandbox folder . Reading and changing audit entries needs the Security privilege ; tests that need
it skip without it and run in CI , whose runners are elevated , like Audit . Tests . ps1 . The -SecurityDescriptor sets
of Add-NTFSAudit and Remove-NTFSAudit have no per-item handler around the change itself ( an open maintainer
item ) , so no test here pins what happens when that change throws for one of several descriptors ; the only
failure the -SecurityDescriptor scenarios below exercise is the already-handled one of a descriptor that was
read without its audit section .
#>
[ Diagnostics . CodeAnalysis . SuppressMessageAttribute (
'PSUseDeclaredVarsMoreThanAssignments' , '' , Justification = 'Pester shares variables between blocks.'
) ]
param ( )
BeforeDiscovery {
Import-Module -Name ( Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1' ) -Force
$canReadAudit = Test-PrivilegeHeld -Name 'SeSecurityPrivilege'
}
BeforeAll {
Import-Module -Name ( Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1' ) -Force
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release\NTFSSecurity.psd1'
Import-Module -Name $modulePath -Force -ErrorAction Stop
$sandbox = New-TestSandbox -Name 'ParameterSetsAudit'
Push-Location -LiteralPath $sandbox
$sidType = [System.Security.Principal.SecurityIdentifier]
$probe = 'S-1-1-0'
$keeper = 'S-1-5-32-545'
$entry = @ { Account = $probe ; AccessRights = 'ReadData' }
function Get-TestAuditSddl {
param ( [ Parameter ( Mandatory ) ] [string] $Path )
( Get-Acl -LiteralPath $Path -Audit ) . GetSecurityDescriptorSddlForm ( 'Audit' )
}
function Get-TestDaclSddl {
param ( [ Parameter ( Mandatory ) ] [string] $Path )
( 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 )
}
}
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
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.
param ( [ Parameter ( Mandatory ) ] [string] $Path )
New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList (
( Get-Item2 -Path $Path ) , [System.Security.AccessControl.AccessControlSections] :: Access
)
}
function Get-TestAccountAuditRule {
param (
[ Parameter ( Mandatory ) ] [System.Security.AccessControl.FileSystemSecurity] $Acl ,
[ Parameter ( Mandatory ) ] [string] $Account
)
@ ( $Acl . GetAuditRules ( $true , $false , $sidType ) | Where-Object -FilterScript { $_ . IdentityReference . Value -eq $Account } )
}
}
AfterAll {
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'Audit cmdlets with a security descriptor that has no audit section' {
# TestAuditSection (#109) handles this the same way for every cmdlet that takes -SecurityDescriptor: Audit.Tests.ps1
# line 425 already asserts the ErrorId for Add-NTFSAudit, Remove-NTFSAudit, and Clear-NTFSAudit; this data set adds
# the category and the target, which must be the descriptor itself, not its path, and also covers Get-NTFSAudit and
# Get-NTFSOrphanedAudit and the Simple parameter sets of Add and Remove. None of this needs the Security privilege:
# the descriptor already carries no audit section before the cmdlet runs.
It '<Command> should write a ReadSecurityError with category InvalidData and the descriptor as target for the <Set> set, and change nothing' -ForEach @ (
@ { Command = 'Add-NTFSAudit' ; Set = 'SDSimple' ; Parameters = @ { Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; AppliesTo = 'ThisFolderOnly' ; PassThru = $true } }
@ { Command = 'Add-NTFSAudit' ; Set = 'SDComplex' ; Parameters = @ { Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; PassThru = $true } }
@ { Command = 'Remove-NTFSAudit' ; Set = 'SDSimple' ; Parameters = @ { Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; AppliesTo = 'ThisFolderOnly' ; PassThru = $true } }
@ { Command = 'Remove-NTFSAudit' ; Set = 'SDComplex' ; Parameters = @ { Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; PassThru = $true } }
@ { Command = 'Clear-NTFSAudit' ; Set = 'SD' ; Parameters = @ { } }
@ { Command = 'Get-NTFSAudit' ; Set = 'SD' ; Parameters = @ { } }
@ { Command = 'Get-NTFSOrphanedAudit' ; Set = 'SD' ; Parameters = @ { } }
) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NoAuditSection'
$sd = Get-TestAccessOnlyDescriptor -Path $file
$result = @ ( & $Command -SecurityDescriptor $sd @Parameters -ErrorVariable auditErrors -ErrorAction SilentlyContinue )
$result | Should -BeNullOrEmpty
$auditErrors | Should -HaveCount 1
$auditErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$auditErrors [ 0 ] . CategoryInfo . Category | Should -Be 'InvalidData'
$auditErrors [ 0 ] . TargetObject | Should -Be $sd
$sd . SecurityDescriptor . GetSecurityDescriptorSddlForm ( 'Audit' ) | Should -BeNullOrEmpty
}
}
Describe 'Add-NTFSAudit' {
Context 'Binding -Path from the pipeline by property name' {
fix(access,audit): bind the AppliesTo property of a piped object for paths
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
declared PathComplex as the default parameter set. An object that had
Path, Account, and AccessRights satisfied the mandatory parameters of
the default set, so PowerShell never tried PathSimple and the cmdlet
dropped the AppliesTo property of the object. Since 4b1abc9 made
-AppliesTo mandatory in the Simple sets, the default set is redundant
for every call that names its parameters, and it masked PathSimple for
the pipeline.
Without a default set, an object that has AppliesTo binds PathSimple,
one that has InheritanceFlags or PropagationFlags binds PathComplex, and
one that has neither binds PathComplex with the default scope. An
object that has AppliesTo and InheritanceFlags now fails with
AmbiguousParameterSet, like the same two named parameters. Named calls,
-SecurityDescriptor calls without AppliesTo, and the round trip
Get-NTFSAccess | Add-NTFSAccess keep their behavior.
The tests pipe objects of each shape and fail without the change. The
cmdlet pages and the help file follow the build.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
# The PathSimple row pipes AppliesTo, which selects the set; the PathSimpleNamed row names -AppliesTo instead, and the
# piped object brings only the path.
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
It 'Should add the audit entry for the piped <Set> object that exists, write a ReadFileError for the one that does not, and leave the DACL unchanged' -ForEach @ (
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
@ { Set = 'PathComplex' ; ExtraProperties = @ { InheritanceFlags = 'None' ; PropagationFlags = 'None' } ; Named = @ { } }
fix(access,audit): bind the AppliesTo property of a piped object for paths
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
declared PathComplex as the default parameter set. An object that had
Path, Account, and AccessRights satisfied the mandatory parameters of
the default set, so PowerShell never tried PathSimple and the cmdlet
dropped the AppliesTo property of the object. Since 4b1abc9 made
-AppliesTo mandatory in the Simple sets, the default set is redundant
for every call that names its parameters, and it masked PathSimple for
the pipeline.
Without a default set, an object that has AppliesTo binds PathSimple,
one that has InheritanceFlags or PropagationFlags binds PathComplex, and
one that has neither binds PathComplex with the default scope. An
object that has AppliesTo and InheritanceFlags now fails with
AmbiguousParameterSet, like the same two named parameters. Named calls,
-SecurityDescriptor calls without AppliesTo, and the round trip
Get-NTFSAccess | Add-NTFSAccess keep their behavior.
The tests pipe objects of each shape and fail without the change. The
cmdlet pages and the help file follow the build.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
@ { Set = 'PathSimple' ; ExtraProperties = @ { AppliesTo = 'ThisFolderOnly' } ; Named = @ { } }
@ { Set = 'PathSimpleNamed' ; ExtraProperties = @ { } ; Named = @ { AppliesTo = 'ThisFolderOnly' } }
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
) -Skip : ( -not $canReadAudit ) {
$missing = Join-Path -Path $sandbox -ChildPath ( 'AddPipeMissing-{0}' -f [guid] :: NewGuid ( ) . ToString ( 'N' ) )
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$good = New-TestSandboxItem -Sandbox $sandbox -Name " AddPipeGood $ Set " -Directory
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$daclBefore = Get-TestDaclSddl -Path $good
$items = @ (
[pscustomobject] ( @ { FullName = $missing ; Account = $probe ; AccessRights = 'ReadData' } + $ExtraProperties )
[pscustomobject] ( @ { FullName = $good ; Account = $probe ; AccessRights = 'ReadData' } + $ExtraProperties )
)
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$result = @ ( $items | Add-NTFSAudit @Named -PassThru -ErrorVariable auditErrors -ErrorAction SilentlyContinue )
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$auditErrors | Should -HaveCount 1
$auditErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
$auditErrors [ 0 ] . CategoryInfo . Category | Should -Be 'OpenError'
$auditErrors [ 0 ] . TargetObject | Should -Be $missing
$result | Should -HaveCount 1
$result [ 0 ] . FullName | Should -Be $good
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
# The default scope of a new entry is ContainerInherit and ObjectInherit; None shows that the scope was honored
$rule = Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $good -Audit ) -Account $probe
$rule | Should -HaveCount 1
$rule [ 0 ] . InheritanceFlags | Should -Be ( [System.Security.AccessControl.InheritanceFlags] :: None )
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
Get-TestDaclSddl -Path $good | Should -BeExactly $daclBefore
}
}
Context 'Binding -SecurityDescriptor from the pipeline by property name' {
It 'Should add the audit entry for the piped <Set> object that has an audit section, write a ReadSecurityError for the one that does not, and leave the item on disk unchanged until Set-NTFSSecurityDescriptor writes it' -ForEach @ (
@ { Set = 'SDSimple' ; ExtraProperties = @ { AppliesTo = 'ThisFolderOnly' } }
@ { Set = 'SDComplex' ; ExtraProperties = @ { InheritanceFlags = 'None' ; PropagationFlags = 'None' } }
) -Skip : ( -not $canReadAudit ) {
$badFile = New-TestSandboxItem -Sandbox $sandbox -Name " AddPipeSDBad $ Set "
$sdBad = Get-TestAccessOnlyDescriptor -Path $badFile
fix(access,audit): bind the AppliesTo property of a piped object for paths
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
declared PathComplex as the default parameter set. An object that had
Path, Account, and AccessRights satisfied the mandatory parameters of
the default set, so PowerShell never tried PathSimple and the cmdlet
dropped the AppliesTo property of the object. Since 4b1abc9 made
-AppliesTo mandatory in the Simple sets, the default set is redundant
for every call that names its parameters, and it masked PathSimple for
the pipeline.
Without a default set, an object that has AppliesTo binds PathSimple,
one that has InheritanceFlags or PropagationFlags binds PathComplex, and
one that has neither binds PathComplex with the default scope. An
object that has AppliesTo and InheritanceFlags now fails with
AmbiguousParameterSet, like the same two named parameters. Named calls,
-SecurityDescriptor calls without AppliesTo, and the round trip
Get-NTFSAccess | Add-NTFSAccess keep their behavior.
The tests pipe objects of each shape and fail without the change. The
cmdlet pages and the help file follow the build.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
# A folder, so that the scope of the entry is observable; the piped AppliesTo property binds SDSimple, as it binds
# PathSimple for a path
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$goodFile = New-TestSandboxItem -Sandbox $sandbox -Name " AddPipeSDGood $ Set " -Directory
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$auditBefore = Get-TestAuditSddl -Path $goodFile
$daclBefore = Get-TestDaclSddl -Path $goodFile
$sdGood = Get-NTFSSecurityDescriptor -Path $goodFile -ErrorAction Stop
$items = @ (
[pscustomobject] ( @ { SecurityDescriptor = $sdBad ; Account = $probe ; AccessRights = 'ReadData' } + $ExtraProperties )
[pscustomobject] ( @ { SecurityDescriptor = $sdGood ; Account = $probe ; AccessRights = 'ReadData' } + $ExtraProperties )
)
$result = @ ( $items | Add-NTFSAudit -PassThru -ErrorVariable auditErrors -ErrorAction SilentlyContinue )
$auditErrors | Should -HaveCount 1
$auditErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$auditErrors [ 0 ] . TargetObject | Should -Be $sdBad
$result | Should -HaveCount 1
$result [ 0 ] . FullName | Should -Be $goodFile
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$rule = Get-TestAccountAuditRule -Acl $sdGood . SecurityDescriptor -Account $probe
$rule | Should -HaveCount 1
$rule [ 0 ] . InheritanceFlags | Should -Be ( [System.Security.AccessControl.InheritanceFlags] :: None )
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
Get-TestAuditSddl -Path $goodFile | Should -BeExactly $auditBefore
Set-NTFSSecurityDescriptor -SecurityDescriptor $sdGood -ErrorAction Stop
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
( Get-TestAccountAuditRule -Acl ( Get-TestAuditAcl -Path $goodFile ) -Account $probe ) | Should -HaveCount 1
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
Get-TestAuditSddl -Path $goodFile | Should -Not -BeExactly $auditBefore
Get-TestDaclSddl -Path $goodFile | Should -BeExactly $daclBefore
}
}
fix(access,audit): bind the AppliesTo property of a piped object for paths
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
declared PathComplex as the default parameter set. An object that had
Path, Account, and AccessRights satisfied the mandatory parameters of
the default set, so PowerShell never tried PathSimple and the cmdlet
dropped the AppliesTo property of the object. Since 4b1abc9 made
-AppliesTo mandatory in the Simple sets, the default set is redundant
for every call that names its parameters, and it masked PathSimple for
the pipeline.
Without a default set, an object that has AppliesTo binds PathSimple,
one that has InheritanceFlags or PropagationFlags binds PathComplex, and
one that has neither binds PathComplex with the default scope. An
object that has AppliesTo and InheritanceFlags now fails with
AmbiguousParameterSet, like the same two named parameters. Named calls,
-SecurityDescriptor calls without AppliesTo, and the round trip
Get-NTFSAccess | Add-NTFSAccess keep their behavior.
The tests pipe objects of each shape and fail without the change. The
cmdlet pages and the help file follow the build.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
# The property AppliesTo selects PathSimple, InheritanceFlags and PropagationFlags select PathComplex, and an object that
# has none of them gets the default scope, as for Add-NTFSAccess (ParameterSets.Access.Tests.ps1).
Context 'Choosing the parameter set of an object bound through the pipeline by property name' {
It 'Should give the audit entry for the piped object that has <Shape> the scope <Scope>' -ForEach @ (
@ { Name = 'AddPipedAuditThisFolderOnly' ; Shape = 'AppliesTo ThisFolderOnly' ; Extra = @ { AppliesTo = 'ThisFolderOnly' } ; Scope = 'None/None' }
@ { Name = 'AddPipedAuditSubfolders' ; Shape = 'AppliesTo ThisFolderAndSubfolders' ; Extra = @ { AppliesTo = 'ThisFolderAndSubfolders' } ; Scope = 'ContainerInherit/None' }
@ { Name = 'AddPipedAuditFlags' ; Shape = 'InheritanceFlags and PropagationFlags' ; Extra = @ { InheritanceFlags = 'ContainerInherit' ; PropagationFlags = 'InheritOnly' } ; Scope = 'ContainerInherit/InheritOnly' }
@ { Name = 'AddPipedAuditNoScope' ; Shape = 'no scope' ; Extra = @ { } ; Scope = 'ContainerInherit, ObjectInherit/None' }
) -Skip : ( -not $canReadAudit ) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name $Name -Directory
[pscustomobject] ( @ { FullName = $folder ; Account = $probe ; AccessRights = 'ReadData' } + $Extra ) | Add-NTFSAudit -ErrorAction Stop
$scopes = @ ( Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $folder -Audit ) -Account $probe |
ForEach-Object -Process { '{0}/{1}' -f $_ . InheritanceFlags , $_ . PropagationFlags } )
( $scopes -join ';' ) | Should -BeExactly $Scope
}
It 'Should reject a piped object that has both AppliesTo and InheritanceFlags with an AmbiguousParameterSet error and change nothing' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'AddPipedAuditBoth' -Directory
$before = Get-TestDaclSddl -Path $folder
$object = [pscustomobject] @ { FullName = $folder ; Account = $probe ; AccessRights = 'ReadData' ; AppliesTo = 'ThisFolderOnly' ; InheritanceFlags = 'None' }
{ $object | Add-NTFSAudit -ErrorAction Stop } | Should -Throw -ErrorId 'AmbiguousParameterSet*'
Get-TestDaclSddl -Path $folder | Should -BeExactly $before
}
}
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
Context 'Conflicting parameters' {
It 'Should reject -AppliesTo combined with -InheritanceFlags with an AmbiguousParameterSet error and change nothing' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'AddAmbiguous'
$before = Get-TestDaclSddl -Path $file
{ Add-NTFSAudit -Path $file -Account $probe -AccessRights ReadData -AppliesTo ThisFolderOnly -InheritanceFlags None -ErrorAction Stop } |
Should -Throw -ErrorId 'AmbiguousParameterSet,NTFSSecurity.AddAudit'
Get-TestDaclSddl -Path $file | Should -BeExactly $before
}
}
Context 'Direct call of -SecurityDescriptor with -AppliesTo' {
# Add-NTFSAudit SDSimple (judged-audit.csv): every existing call binds it only through a splat
# (PermissionScopes.Tests.ps1) or through position (Audit.Tests.ps1); this one names the set's parameters
# directly.
It 'Should add the entry to the descriptor when -SecurityDescriptor, -Account, -AccessRights, and -AppliesTo are all named directly' -Skip : ( -not $canReadAudit ) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'AddDirectSDSimple'
$sd = Get-NTFSSecurityDescriptor -Path $file -ErrorAction Stop
$result = @ ( Add-NTFSAudit -SecurityDescriptor $sd -Account $probe -AccessRights ReadData -AppliesTo ThisFolderOnly -PassThru -ErrorAction Stop )
$result | Should -HaveCount 1
$result [ 0 ] | Should -BeOfType [Security2.FileSystemAuditRule2]
( Get-TestAccountAuditRule -Acl $sd . SecurityDescriptor -Account $probe ) | Should -HaveCount 1
}
}
}
Describe 'Remove-NTFSAudit' {
Context 'Binding -Path from the pipeline by property name' {
fix(access,audit): bind the AppliesTo property of a piped object for paths
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
declared PathComplex as the default parameter set. An object that had
Path, Account, and AccessRights satisfied the mandatory parameters of
the default set, so PowerShell never tried PathSimple and the cmdlet
dropped the AppliesTo property of the object. Since 4b1abc9 made
-AppliesTo mandatory in the Simple sets, the default set is redundant
for every call that names its parameters, and it masked PathSimple for
the pipeline.
Without a default set, an object that has AppliesTo binds PathSimple,
one that has InheritanceFlags or PropagationFlags binds PathComplex, and
one that has neither binds PathComplex with the default scope. An
object that has AppliesTo and InheritanceFlags now fails with
AmbiguousParameterSet, like the same two named parameters. Named calls,
-SecurityDescriptor calls without AppliesTo, and the round trip
Get-NTFSAccess | Add-NTFSAccess keep their behavior.
The tests pipe objects of each shape and fail without the change. The
cmdlet pages and the help file follow the build.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
# The PathSimple row pipes AppliesTo, which selects the set; the PathSimpleNamed row names -AppliesTo instead, and the
# piped object brings only the path.
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
It " Should remove the audit entry for the piped <Set> object that exists, write a ReadFileError for the one that does not, and keep the other account's entry " -ForEach @ (
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
@ { Set = 'PathComplex' ; ExtraProperties = @ { InheritanceFlags = 'None' ; PropagationFlags = 'None' } ; Named = @ { } }
fix(access,audit): bind the AppliesTo property of a piped object for paths
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
declared PathComplex as the default parameter set. An object that had
Path, Account, and AccessRights satisfied the mandatory parameters of
the default set, so PowerShell never tried PathSimple and the cmdlet
dropped the AppliesTo property of the object. Since 4b1abc9 made
-AppliesTo mandatory in the Simple sets, the default set is redundant
for every call that names its parameters, and it masked PathSimple for
the pipeline.
Without a default set, an object that has AppliesTo binds PathSimple,
one that has InheritanceFlags or PropagationFlags binds PathComplex, and
one that has neither binds PathComplex with the default scope. An
object that has AppliesTo and InheritanceFlags now fails with
AmbiguousParameterSet, like the same two named parameters. Named calls,
-SecurityDescriptor calls without AppliesTo, and the round trip
Get-NTFSAccess | Add-NTFSAccess keep their behavior.
The tests pipe objects of each shape and fail without the change. The
cmdlet pages and the help file follow the build.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
@ { Set = 'PathSimple' ; ExtraProperties = @ { AppliesTo = 'ThisFolderOnly' } ; Named = @ { } }
@ { Set = 'PathSimpleNamed' ; ExtraProperties = @ { } ; Named = @ { AppliesTo = 'ThisFolderOnly' } }
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
) -Skip : ( -not $canReadAudit ) {
$missing = Join-Path -Path $sandbox -ChildPath ( 'RemovePipeMissing-{0}' -f [guid] :: NewGuid ( ) . ToString ( 'N' ) )
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing
$good = New-TestSandboxItem -Sandbox $sandbox -Name " RemovePipeGood $ Set "
Add-NTFSAudit -Path $good @entry -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
Add-NTFSAudit -Path $good -Account $keeper -AccessRights Delete -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$daclBefore = Get-TestDaclSddl -Path $good
$items = @ (
[pscustomobject] ( @ { FullName = $missing ; Account = $probe ; AccessRights = 'ReadData' } + $ExtraProperties )
[pscustomobject] ( @ { FullName = $good ; Account = $probe ; AccessRights = 'ReadData' } + $ExtraProperties )
)
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$result = @ ( $items | Remove-NTFSAudit @Named -PassThru -ErrorVariable auditErrors -ErrorAction SilentlyContinue )
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$auditErrors | Should -HaveCount 1
$auditErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
$auditErrors [ 0 ] . CategoryInfo . Category | Should -Be 'OpenError'
$auditErrors [ 0 ] . TargetObject | Should -Be $missing
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_ . FullName | Should -Be $good }
( Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $good -Audit ) -Account $probe ) | Should -BeNullOrEmpty
( Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $good -Audit ) -Account $keeper ) | Should -HaveCount 1
Get-TestDaclSddl -Path $good | Should -BeExactly $daclBefore
}
}
Context 'Binding -SecurityDescriptor from the pipeline by property name' {
It " Should remove the audit entry for the piped <Set> object that has an audit section, write a ReadSecurityError for the one that does not, and keep the other account's entry " -ForEach @ (
@ { Set = 'SDSimple' ; ExtraProperties = @ { AppliesTo = 'ThisFolderOnly' } }
@ { Set = 'SDComplex' ; ExtraProperties = @ { InheritanceFlags = 'None' ; PropagationFlags = 'None' } }
) -Skip : ( -not $canReadAudit ) {
$badFile = New-TestSandboxItem -Sandbox $sandbox -Name " RemovePipeSDBad $ Set "
$sdBad = Get-TestAccessOnlyDescriptor -Path $badFile
$goodFile = New-TestSandboxItem -Sandbox $sandbox -Name " RemovePipeSDGood $ Set "
Add-NTFSAudit -Path $goodFile @entry -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
Add-NTFSAudit -Path $goodFile -Account $keeper -AccessRights Delete -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$sdGood = Get-NTFSSecurityDescriptor -Path $goodFile -ErrorAction Stop
$items = @ (
[pscustomobject] ( @ { SecurityDescriptor = $sdBad ; Account = $probe ; AccessRights = 'ReadData' } + $ExtraProperties )
[pscustomobject] ( @ { SecurityDescriptor = $sdGood ; Account = $probe ; AccessRights = 'ReadData' } + $ExtraProperties )
)
$result = @ ( $items | Remove-NTFSAudit -PassThru -ErrorVariable auditErrors -ErrorAction SilentlyContinue )
$auditErrors | Should -HaveCount 1
$auditErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$auditErrors [ 0 ] . TargetObject | Should -Be $sdBad
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_ . FullName | Should -Be $goodFile }
( Get-TestAccountAuditRule -Acl $sdGood . SecurityDescriptor -Account $probe ) | Should -BeNullOrEmpty
( Get-TestAccountAuditRule -Acl $sdGood . SecurityDescriptor -Account $keeper ) | Should -HaveCount 1
}
}
fix(access,audit): bind the AppliesTo property of a piped object for paths
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
declared PathComplex as the default parameter set. An object that had
Path, Account, and AccessRights satisfied the mandatory parameters of
the default set, so PowerShell never tried PathSimple and the cmdlet
dropped the AppliesTo property of the object. Since 4b1abc9 made
-AppliesTo mandatory in the Simple sets, the default set is redundant
for every call that names its parameters, and it masked PathSimple for
the pipeline.
Without a default set, an object that has AppliesTo binds PathSimple,
one that has InheritanceFlags or PropagationFlags binds PathComplex, and
one that has neither binds PathComplex with the default scope. An
object that has AppliesTo and InheritanceFlags now fails with
AmbiguousParameterSet, like the same two named parameters. Named calls,
-SecurityDescriptor calls without AppliesTo, and the round trip
Get-NTFSAccess | Add-NTFSAccess keep their behavior.
The tests pipe objects of each shape and fail without the change. The
cmdlet pages and the help file follow the build.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
# As for Add-NTFSAudit, the property AppliesTo of the piped object selects PathSimple, and the entry that the cmdlet
# addresses is the one of the scope it names: the rest of an entry of a wider scope stays.
Context 'Choosing the parameter set of an object bound through the pipeline by property name' {
It 'Should leave <Meaning> of an audit entry that applies to this folder, subfolders, and files when the piped object that has <Shape> removes its rights' -ForEach @ (
@ { Name = 'RemovePipedAuditThisFolderOnly' ; Shape = 'AppliesTo ThisFolderOnly' ; Extra = @ { AppliesTo = 'ThisFolderOnly' } ; Meaning = 'the part that only subfolders and files inherit' ; Left = 'ContainerInherit, ObjectInherit/InheritOnly' }
@ { Name = 'RemovePipedAuditNoScope' ; Shape = 'no scope' ; Extra = @ { } ; Meaning = 'nothing' ; Left = '' }
) -Skip : ( -not $canReadAudit ) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name $Name -Directory
Add-NTFSAudit -Path $folder @entry -ErrorAction Stop
[pscustomobject] ( @ { FullName = $folder ; Account = $probe ; AccessRights = 'ReadData' } + $Extra ) | Remove-NTFSAudit -ErrorAction Stop
$remaining = @ ( Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $folder -Audit ) -Account $probe |
ForEach-Object -Process { '{0}/{1}' -f $_ . InheritanceFlags , $_ . PropagationFlags } )
( $remaining -join ';' ) | Should -BeExactly $Left
}
It 'Should reject a piped object that has both AppliesTo and InheritanceFlags with an AmbiguousParameterSet error and change nothing' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'RemovePipedAuditBoth' -Directory
$before = Get-TestDaclSddl -Path $folder
$object = [pscustomobject] @ { FullName = $folder ; Account = $probe ; AccessRights = 'ReadData' ; AppliesTo = 'ThisFolderOnly' ; InheritanceFlags = 'None' }
{ $object | Remove-NTFSAudit -ErrorAction Stop } | Should -Throw -ErrorId 'AmbiguousParameterSet*'
Get-TestDaclSddl -Path $folder | Should -BeExactly $before
}
}
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
Context 'Without -RemoveSpecific' {
# Judged-audit-notes.md: "the Modify entry keeps the other rights (probe first, then pin)".
It 'Should narrow an entry that grants more rights than it removes, instead of deleting it' -Skip : ( -not $canReadAudit ) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveNarrow'
Add-NTFSAudit -Path $file -Account $probe -AccessRights Modify -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
Add-NTFSAudit -Path $file -Account $keeper -AccessRights Delete -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
Remove-NTFSAudit -Path $file @entry -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$probeRule = Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $file -Audit ) -Account $probe
$probeRule | Should -HaveCount 1
$probeRule [ 0 ] . FileSystemRights . HasFlag ( [System.Security.AccessControl.FileSystemRights] :: ReadData ) | Should -BeFalse
$probeRule [ 0 ] . FileSystemRights . HasFlag ( [System.Security.AccessControl.FileSystemRights] :: Delete ) | Should -BeTrue
( Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $file -Audit ) -Account $keeper ) | Should -HaveCount 1
}
}
Context 'Direct call of -Path with -AppliesTo' {
# Remove-NTFSAudit PathSimple (judged-audit.csv): every existing call binds it only through a splat
# (PermissionScopes.Tests.ps1); this one names the set's parameters directly.
It 'Should remove the entry from the path when -Path, -Account, -AccessRights, and -AppliesTo are all named directly' -Skip : ( -not $canReadAudit ) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveDirectPathSimple'
Add-NTFSAudit -Path $file -Account $probe -AccessRights ReadData -AppliesTo ThisFolderOnly -ErrorAction Stop
Add-NTFSAudit -Path $file -Account $keeper -AccessRights Delete -AppliesTo ThisFolderOnly -ErrorAction Stop
$result = @ ( Remove-NTFSAudit -Path $file -Account $probe -AccessRights ReadData -AppliesTo ThisFolderOnly -PassThru -ErrorAction Stop )
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_ | Should -BeOfType [Security2.FileSystemAuditRule2] }
( Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $file -Audit ) -Account $probe ) | Should -BeNullOrEmpty
( Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $file -Audit ) -Account $keeper ) | Should -HaveCount 1
}
}
Context 'Direct call of -SecurityDescriptor with -AppliesTo' {
# Remove-NTFSAudit SDSimple (judged-audit.csv): every existing call binds it only through a splat
# (PermissionScopes.Tests.ps1); this one names the set's parameters directly.
It 'Should remove the entry from the descriptor when -SecurityDescriptor, -Account, -AccessRights, and -AppliesTo are all named directly' -Skip : ( -not $canReadAudit ) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveDirectSDSimple'
$sd = Get-NTFSSecurityDescriptor -Path $file -ErrorAction Stop
Add-NTFSAudit -SecurityDescriptor $sd -Account $probe -AccessRights ReadData -AppliesTo ThisFolderOnly -ErrorAction Stop
Add-NTFSAudit -SecurityDescriptor $sd -Account $keeper -AccessRights Delete -AppliesTo ThisFolderOnly -ErrorAction Stop
$result = @ ( Remove-NTFSAudit -SecurityDescriptor $sd -Account $probe -AccessRights ReadData -AppliesTo ThisFolderOnly -PassThru -ErrorAction Stop )
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_ | Should -BeOfType [Security2.FileSystemAuditRule2] }
( Get-TestAccountAuditRule -Acl $sd . SecurityDescriptor -Account $probe ) | Should -BeNullOrEmpty
( Get-TestAccountAuditRule -Acl $sd . SecurityDescriptor -Account $keeper ) | Should -HaveCount 1
}
}
}
Describe 'Clear-NTFSAudit' {
Context 'Taking paths from the pipeline' {
It 'Should clear the audit entries of each path taken from the pipeline and leave the DACL of each unchanged' -Skip : ( -not $canReadAudit ) {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'ClearPipeFirst'
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'ClearPipeSecond'
foreach ( $path in $first , $second ) {
Add-NTFSAudit -Path $path @entry -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
}
$daclBefore = @ ( ( Get-TestDaclSddl -Path $first ) , ( Get-TestDaclSddl -Path $second ) )
$first , $second | Clear-NTFSAudit -ErrorVariable clearErrors -ErrorAction SilentlyContinue
$clearErrors | Should -BeNullOrEmpty
@ ( Get-NTFSAudit -Path $first -ExcludeInherited ) | Should -BeNullOrEmpty
@ ( Get-NTFSAudit -Path $second -ExcludeInherited ) | Should -BeNullOrEmpty
Get-TestDaclSddl -Path $first | Should -BeExactly $daclBefore [ 0 ]
Get-TestDaclSddl -Path $second | Should -BeExactly $daclBefore [ 1 ]
}
}
Context 'With -DisableInheritance' {
# judged-audit.csv: "-DisableInheritance on a path is passed (Audit.Tests.ps1:629) but its end state
# (protected SACL, no copied inherited entries) is never asserted."
It " Should protect the SACL and not copy the parent's inherited entry when clearing a child path " -Skip : ( -not $canReadAudit ) {
$parent = New-TestSandboxItem -Sandbox $sandbox -Name 'ClearDisableParent' -Directory
Add-NTFSAudit -Path $parent -Account $keeper -AccessRights Delete -AppliesTo ThisFolderAndFiles -AuditFlags Success -ErrorAction Stop
$child = Join-Path -Path $parent -ChildPath 'Child.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $child
Set-Content -LiteralPath $child -Value 'Child'
Add-NTFSAudit -Path $child @entry -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
# Precondition: the parent's rule really is inherited onto the child before the test clears it.
@ ( Get-NTFSAudit -Path $child -ExcludeExplicit ) | Should -HaveCount 1
$daclBefore = Get-TestDaclSddl -Path $child
Clear-NTFSAudit -Path $child -DisableInheritance -ErrorVariable clearErrors -ErrorAction SilentlyContinue
$clearErrors | Should -BeNullOrEmpty
( Get-TestAuditAcl -Path $child ) . AreAuditRulesProtected | Should -BeTrue
test(audit): assert the parameter sets that the judged matrix found untested
Add Tests\ParameterSets.Audit.Tests.ps1, closing the TestNeeded gaps that
judged-audit.csv/judged-audit-notes.md recorded for Add-NTFSAudit,
Remove-NTFSAudit, Clear-NTFSAudit, Get-NTFSAudit, and Get-NTFSOrphanedAudit:
- Pipeline binding by value and by property name for every parameter set of
all five cmdlets, including the four sets the matrix flagged as bound only
by splat or pipe calls (Add-NTFSAudit SDSimple, Remove-NTFSAudit PathSimple
and SDSimple, Get-NTFSOrphanedAudit SD), each of which also gets a direct,
named call.
- Continuation past a failing path or a descriptor without an audit section,
asserting the ErrorId, category, and target of the error and the state or
output of the item that follows.
- State assertions beyond -PassThru: SACL/DACL SDDL before and after, the
in-memory descriptor versus the item on disk until Set-NTFSSecurityDescriptor
writes it, and the protected/unprotected SACL after Clear-NTFSAudit
-DisableInheritance.
- Narrowing of an audit entry's rights when Remove-NTFSAudit runs without
-RemoveSpecific, and the AmbiguousParameterSet error when -AppliesTo and
-InheritanceFlags are combined.
No production code, existing test file, or helper module changed. Fixture
DACLs use .NET SetAccessControl through the existing TestHelpers.psm1
helpers; no test pins the open item of the -SecurityDescriptor sets of Add
and Remove having no per-item handler around the change itself.
Verified in all four configurations (elevated/basic user x Windows
PowerShell 5.1/PowerShell 7): 0 failures. Mutation-tested with 9 one-statement
changes to the production C# in a separate, detached worktree; 8 were caught
by the new tests and one (the -AppliesTo-to-flags conversion for the Simple
sets, masked on a file target) was not, which the pre-existing
PermissionScopes.Tests.ps1 already catches on a folder target.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
@ ( ( Get-Acl -LiteralPath $child -Audit ) . GetAuditRules ( $true , $true , $sidType ) ) | Should -BeNullOrEmpty
Get-TestDaclSddl -Path $child | Should -BeExactly $daclBefore
}
}
Context 'Binding -SecurityDescriptor from the pipeline' {
It 'Should clear each piped descriptor that has an audit section and continue past one that does not' -Skip : ( -not $canReadAudit ) {
$badFile = New-TestSandboxItem -Sandbox $sandbox -Name 'ClearPipeSDBad'
$sdBad = Get-TestAccessOnlyDescriptor -Path $badFile
$goodFile = New-TestSandboxItem -Sandbox $sandbox -Name 'ClearPipeSDGood'
Add-NTFSAudit -Path $goodFile @entry -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$sdGood = Get-NTFSSecurityDescriptor -Path $goodFile -ErrorAction Stop
$auditBefore = Get-TestAuditSddl -Path $goodFile
$daclBefore = Get-TestDaclSddl -Path $goodFile
$sdBad , $sdGood | Clear-NTFSAudit -ErrorVariable clearErrors -ErrorAction SilentlyContinue
$clearErrors | Should -HaveCount 1
$clearErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$clearErrors [ 0 ] . TargetObject | Should -Be $sdBad
@ ( $sdGood . SecurityDescriptor . GetAuditRules ( $true , $false , $sidType ) ) | Should -BeNullOrEmpty
Get-TestAuditSddl -Path $goodFile | Should -BeExactly $auditBefore
Set-NTFSSecurityDescriptor -SecurityDescriptor $sdGood -ErrorAction Stop
@ ( Get-NTFSAudit -Path $goodFile -ExcludeInherited ) | Should -BeNullOrEmpty
Get-TestDaclSddl -Path $goodFile | Should -BeExactly $daclBefore
}
}
}
Describe 'Get-NTFSAudit' {
Context 'With several paths' {
# judged-audit.csv observation: Audit.Tests.ps1:70-73 no longer reproduces a failing path followed by one
# that succeeds (the deny entry blocks the DACL, not the SACL that the cmdlet reads). A missing path does.
It 'Should return the entries of the existing path in either position and a ReadFileError, category OpenError, for the missing one' -Skip : ( -not $canReadAudit ) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'GetAuditFolder' -Directory
Add-NTFSAudit -Path $folder -Account $probe -AccessRights ReadData -AuditFlags Success -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$missing = Join-Path -Path $sandbox -ChildPath ( 'GetAuditMissing-{0}' -f [guid] :: NewGuid ( ) . ToString ( 'N' ) )
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing
$forward = @ ( Get-NTFSAudit -Path $missing , $folder -ErrorVariable forwardErrors -ErrorAction SilentlyContinue )
$backward = @ ( Get-NTFSAudit -Path $folder , $missing -ErrorVariable backwardErrors -ErrorAction SilentlyContinue )
foreach ( $errors in $forwardErrors , $backwardErrors ) {
$errors | Should -HaveCount 1
$errors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
$errors [ 0 ] . CategoryInfo . Category | Should -Be 'OpenError'
$errors [ 0 ] . TargetObject | Should -Be $missing
}
foreach ( $result in $forward , $backward ) {
$result | Should -HaveCount 1
$result [ 0 ] | Should -BeOfType [Security2.FileSystemAuditRule2]
$result [ 0 ] . FullName | Should -Be $folder
$result [ 0 ] . Account . Sid | Should -Be $probe
$result [ 0 ] . AccessRights . HasFlag ( [Security2.FileSystemRights2] :: ReadData ) | Should -BeTrue
$result [ 0 ] . AuditFlags | Should -Be ( [System.Security.AccessControl.AuditFlags] :: Success )
$result [ 0 ] . IsInherited | Should -BeFalse
$result [ 0 ] . InheritanceEnabled | Should -Be ( -not ( Get-Acl -LiteralPath $folder -Audit ) . AreAuditRulesProtected )
}
}
}
Context 'Taking paths from the pipeline' {
It 'Should return the entries of each path object taken from the pipeline by value' -Skip : ( -not $canReadAudit ) {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'GetAuditPipeFirst'
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'GetAuditPipeSecond'
foreach ( $path in $first , $second ) {
Add-NTFSAudit -Path $path @entry -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
}
$result = @ ( Get-Item2 -Path $first , $second | Get-NTFSAudit -ErrorAction Stop )
( $result . FullName | Sort-Object ) -join '|' | Should -Be ( ( $first , $second | Sort-Object ) -join '|' )
}
}
Context 'When the audit entries cannot be read' {
# judged-audit.csv: Audit.Tests.ps1:48 asserts the ErrorId only; this adds the category and the target.
It 'Should write a ReadSecurityError with category OpenError and the path as target without the Security privilege' -Skip : $canReadAudit {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'GetAuditNoPrivilege'
$entries = @ ( Get-NTFSAudit -Path $file -ErrorVariable auditErrors -ErrorAction SilentlyContinue )
$entries | Should -BeNullOrEmpty
$auditErrors | Should -HaveCount 1
$auditErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$auditErrors [ 0 ] . CategoryInfo . Category | Should -Be 'OpenError'
$auditErrors [ 0 ] . TargetObject | Should -Be $file
}
}
Context 'Binding -SecurityDescriptor from the pipeline' {
It 'Should return the entries of the piped descriptor that has an audit section and continue past one that does not' -Skip : ( -not $canReadAudit ) {
$badFile = New-TestSandboxItem -Sandbox $sandbox -Name 'GetAuditSDBad'
$sdBad = Get-TestAccessOnlyDescriptor -Path $badFile
$goodFile = New-TestSandboxItem -Sandbox $sandbox -Name 'GetAuditSDGood'
Add-NTFSAudit -Path $goodFile @entry -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$expected = @ ( Get-NTFSAudit -Path $goodFile -ErrorAction Stop | ForEach-Object -Process { '{0}|{1}|{2}' -f $_ . Account . Sid , $_ . AccessRights , $_ . AuditFlags } )
$sdGood = Get-NTFSSecurityDescriptor -Path $goodFile -ErrorAction Stop
$result = @ ( $sdBad , $sdGood | Get-NTFSAudit -ErrorVariable auditErrors -ErrorAction SilentlyContinue )
$auditErrors | Should -HaveCount 1
$auditErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$auditErrors [ 0 ] . CategoryInfo . Category | Should -Be 'InvalidData'
$auditErrors [ 0 ] . TargetObject | Should -Be $sdBad
$result | Should -Not -BeNullOrEmpty
( $result | ForEach-Object -Process { '{0}|{1}|{2}' -f $_ . Account . Sid , $_ . AccessRights , $_ . AuditFlags } ) -join ';' | Should -Be ( $expected -join ';' )
}
}
}
Describe 'Get-NTFSOrphanedAudit' {
Context 'Taking paths from the pipeline' {
It 'Should return the orphaned entries of each path taken from the pipeline and write a ReadError, category OpenError, for one that does not exist' -Skip : ( -not $canReadAudit ) {
$orphaned = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedPipe'
Add-NTFSAudit -Path $orphaned -Account 'S-1-5-21-1-2-3-9001' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$normal = New-TestSandboxItem -Sandbox $sandbox -Name 'NormalPipe'
Add-NTFSAudit -Path $normal @entry -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$missing = Join-Path -Path $sandbox -ChildPath ( 'OrphanedMissing-{0}' -f [guid] :: NewGuid ( ) . ToString ( 'N' ) )
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing
$result = @ ( $missing , $orphaned , $normal | Get-NTFSOrphanedAudit -ErrorVariable orphanedErrors -ErrorAction SilentlyContinue )
$orphanedErrors | Should -HaveCount 1
$orphanedErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ReadError,*'
$orphanedErrors [ 0 ] . CategoryInfo . Category | Should -Be 'OpenError'
$orphanedErrors [ 0 ] . TargetObject | Should -Be $missing
$result | Should -HaveCount 1
$result [ 0 ] . FullName | Should -Be $orphaned
$result [ 0 ] . Account . Sid | Should -Be 'S-1-5-21-1-2-3-9001'
}
}
Context 'Binding -SecurityDescriptor from the pipeline' {
It 'Should return the orphaned entries of the piped descriptor that has an audit section and continue past one that does not' -Skip : ( -not $canReadAudit ) {
$badFile = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedSDBad'
$sdBad = Get-TestAccessOnlyDescriptor -Path $badFile
$orphanedFile = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedSDGood'
Add-NTFSAudit -Path $orphanedFile -Account 'S-1-5-21-1-2-3-9002' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$sdOrphaned = Get-NTFSSecurityDescriptor -Path $orphanedFile -ErrorAction Stop
$result = @ ( $sdBad , $sdOrphaned | Get-NTFSOrphanedAudit -ErrorVariable orphanedErrors -ErrorAction SilentlyContinue )
$orphanedErrors | Should -HaveCount 1
$orphanedErrors [ 0 ] . TargetObject | Should -Be $sdBad
$result | Should -HaveCount 1
$result [ 0 ] | Should -BeOfType [Security2.FileSystemAuditRule2]
$result [ 0 ] . Account . Sid | Should -Be 'S-1-5-21-1-2-3-9002'
$result [ 0 ] . FullName | Should -Be $orphanedFile
}
}
Context 'Direct call of -SecurityDescriptor' {
# Get-NTFSOrphanedAudit SD (judged-audit.csv): both existing calls bind it only because PowerShell resolves
# a piped object with a SecurityDescriptor property to this parameter (Audit.Tests.ps1:243, :294); this one
# names it directly.
It 'Should return the orphaned entries when -SecurityDescriptor is named directly' -Skip : ( -not $canReadAudit ) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedDirectSD'
Add-NTFSAudit -Path $file -Account 'S-1-5-21-1-2-3-9003' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$sd = Get-NTFSSecurityDescriptor -Path $file -ErrorAction Stop
$result = @ ( Get-NTFSOrphanedAudit -SecurityDescriptor $sd -ErrorAction Stop )
$result | Should -HaveCount 1
$result [ 0 ] | Should -BeOfType [Security2.FileSystemAuditRule2]
$result [ 0 ] . Account . Sid | Should -Be 'S-1-5-21-1-2-3-9003'
$result [ 0 ] . FullName | Should -Be $file
}
}
}