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 } )
}
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
function Get-TestAuditScope {
# The scopes of the explicit audit entries of the probe account, each as 'inheritance flags/propagation flags', joined by ';'
param ( [ Parameter ( Mandatory ) ] [System.Security.AccessControl.FileSystemSecurity] $Acl )
@ ( Get-TestAccountAuditRule -Acl $Acl -Account $probe |
ForEach-Object -Process { '{0}/{1}' -f $_ . InheritanceFlags , $_ . PropagationFlags } ) -join ';'
}
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
}
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
}
}
test(audit): close budget gaps for Add/Remove/Clear/Get-NTFSAudit and Get-NTFSOrphanedAudit
Closes the eight remaining 'budget' parameter-set gaps that
Quality-Gate-Unknowns-2026-10-10-ParameterSets.csv lists for the audit
cmdlets, in Tests\ParameterSets.Audit.Tests.ps1 only:
- Add-NTFSAudit PathComplex: binds -Account (several accounts) and
-AuditFlags from a single piped object's property names in one call.
- Remove-NTFSAudit PathComplex: removes for several accounts named in one
call, and leaves the empty Audit SDDL and the DACL unchanged for an item
that has no SACL.
- Remove-NTFSAudit SDSimple and SDComplex: proves the item on disk keeps
its audit section until Set-NTFSSecurityDescriptor writes the change,
and that -PassThru returns FileSystemAuditRule2 objects for both sets.
- Clear-NTFSAudit Path: asserts the cleared state of the succeeding path
that follows a missing one in the same pipeline.
- Get-NTFSAudit SD: asserts the output's type, account, rights, flags, and
inherited state directly, independent of the Path set's output.
- Get-NTFSOrphanedAudit Path: adds the OpenError category of the
ReadSecurityError without the Security privilege (PermissionDenied needs
a remote/lab target and is not in scope), and -ExcludeExplicit.
- Get-NTFSOrphanedAudit SD: adds -Account and -ExcludeInherited/
-ExcludeExplicit filters against a piped security descriptor.
Every new test reads state back with an independent oracle (Get-Acl
-Audit or the .NET SACL-only accessor), and the Set-NTFSSecurityDescriptor
cases confirm the SD cmdlets change the item only in memory. The owner
retry after access denied (Limit) and the open maintainer item on
exceptions from the SD change itself (MaintainerDecision) are left out, as
the worklist directs.
9 new It blocks (10 executed cases; one is split SDSimple/SDComplex by
-ForEach) were verified test-first: a mutated copy with one flipped
expectation per new It failed only those 10 cases in the configurations
where they run, and nothing else; three repeated runs of the real file in
all four configurations (Windows PowerShell 5.1 and PowerShell 7, elevated
and basic user) gave identical, all-green results; and
Invoke-ScriptAnalyzer reports nothing, matching the base commit's file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
Context 'Binding -Account and -AuditFlags from the pipeline by property name' {
# judged-audit.csv (Add-NTFSAudit PathComplex): no existing call binds -AuditFlags by property name, and
# none adds for several accounts in one call; this piped object binds both at once.
It 'Should add one audit entry for each account that the piped object names, with the AuditFlags it also names' -Skip : ( -not $canReadAudit ) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'AddPipedAuditSeveralAccounts' -Directory
[pscustomobject] @ { FullName = $folder ; Account = @ ( $probe , $keeper ) ; AccessRights = 'ReadData' ; AuditFlags = 'Failure' ; InheritanceFlags = 'None' ; PropagationFlags = 'None' } | Add-NTFSAudit -ErrorAction Stop
$acl = Get-Acl -LiteralPath $folder -Audit
foreach ( $account in $probe , $keeper ) {
$rule = Get-TestAccountAuditRule -Acl $acl -Account $account
$rule | Should -HaveCount 1
$rule [ 0 ] . AuditFlags | Should -Be ( [System.Security.AccessControl.AuditFlags] :: Failure )
$rule [ 0 ] . FileSystemRights . HasFlag ( [System.Security.AccessControl.FileSystemRights] :: ReadData ) | 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
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
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
Get-TestAuditScope -Acl ( Get-Acl -LiteralPath $folder -Audit ) | Should -BeExactly $Scope
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
}
It 'Should reject a piped object that has both AppliesTo and InheritanceFlags with an AmbiguousParameterSet error' {
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
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'AddPipedAuditBoth' -Directory
$object = [pscustomobject] @ { FullName = $folder ; Account = $probe ; AccessRights = 'ReadData' ; AppliesTo = 'ThisFolderOnly' ; InheritanceFlags = 'None' }
{ $object | Add-NTFSAudit -ErrorAction Stop } | Should -Throw -ErrorId 'AmbiguousParameterSet*'
}
}
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
# PowerShell resets only the parameters that the previous object bound from the pipeline. The scope that the cmdlet
# derives from AppliesTo must therefore not stay in the fields behind InheritanceFlags and PropagationFlags: the next
# object that has no scope, or only one flag, would get it (ParameterSets.Access.Tests.ps1 has the same rows).
Context 'Objects with different scopes in one pipeline' {
It 'Should give each piped path object only its own scope: AppliesTo, none, AppliesTo, one flag, none' -Skip : ( -not $canReadAudit ) {
$folders = @ ( 'AddMixedAuditThisFolderOnly' , 'AddMixedAuditNoScope' , 'AddMixedAuditInheritOnly' , 'AddMixedAuditOneFlag' , 'AddMixedAuditNoScopeAgain' |
ForEach-Object -Process { New-TestSandboxItem -Sandbox $sandbox -Name $_ -Directory } )
@ (
[pscustomobject] ( $entry + @ { FullName = $folders [ 0 ] ; AppliesTo = 'ThisFolderOnly' } )
[pscustomobject] ( $entry + @ { FullName = $folders [ 1 ] } )
[pscustomobject] ( $entry + @ { FullName = $folders [ 2 ] ; AppliesTo = 'SubfoldersAndFilesOnly' } )
[pscustomobject] ( $entry + @ { FullName = $folders [ 3 ] ; InheritanceFlags = 'ContainerInherit' } )
[pscustomobject] ( $entry + @ { FullName = $folders [ 4 ] } )
) | Add-NTFSAudit -ErrorAction Stop
$scopes = $folders | ForEach-Object -Process { Get-TestAuditScope -Acl ( Get-Acl -LiteralPath $_ -Audit ) }
$scopes -join '|' | Should -BeExactly 'None/None|ContainerInherit, ObjectInherit/None|ContainerInherit, ObjectInherit/InheritOnly|ContainerInherit/None|ContainerInherit, ObjectInherit/None'
}
docs(access,audit): say what an empty or null AppliesTo does, and test the Import-Csv route
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
It 'Should give each row of a CSV file that Import-Csv reads and that has an AppliesTo column its own scope' -Skip : ( -not $canReadAudit ) {
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$folders = @ ( 'AddCsvAuditThisFolderOnly' , 'AddCsvAuditSubfolders' , 'AddCsvAuditInheritOnly' |
ForEach-Object -Process { New-TestSandboxItem -Sandbox $sandbox -Name $_ -Directory } )
docs(access,audit): say what an empty or null AppliesTo does, and test the Import-Csv route
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$csv = Join-Path -Path $sandbox -ChildPath 'AddCsvAuditScopes.csv'
Assert-TestSandboxPath -Sandbox $sandbox -Path $csv
Set-Content -LiteralPath $csv -Value @ (
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
'FullName,Account,AccessRights,AppliesTo'
( '"{0}",{1},ReadData,ThisFolderOnly' -f $folders [ 0 ] , $probe )
( '"{0}",{1},ReadData,ThisFolderAndSubfolders' -f $folders [ 1 ] , $probe )
( '"{0}",{1},ReadData,SubfoldersAndFilesOnly' -f $folders [ 2 ] , $probe )
docs(access,audit): say what an empty or null AppliesTo does, and test the Import-Csv route
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
)
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
docs(access,audit): say what an empty or null AppliesTo does, and test the Import-Csv route
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
Import-Csv -LiteralPath $csv | Add-NTFSAudit -ErrorAction Stop
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$scopes = $folders | ForEach-Object -Process { Get-TestAuditScope -Acl ( Get-Acl -LiteralPath $_ -Audit ) }
$scopes -join '|' | Should -BeExactly 'None/None|ContainerInherit/None|ContainerInherit, ObjectInherit/InheritOnly'
}
fix(access): report a piped AccessType, AuditFlags, flags, or scope that isn't valid
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
silently kept the default value of AccessType, AuditFlags,
InheritanceFlags, PropagationFlags, and AppliesTo when a piped object's
property could not be converted to the parameter's type: a misspelled
AccessType such as "Denyy", or a blank CSV cell, created an Allow entry
with no message. For a permissions tool, a typo for Deny that silently
grants access is a security-relevant defect (Decision 27).
The five parameters are now declared as object and converted inside
the cmdlet, the way the PowerShell binder converts a typed parameter
(names in any case, comma-separated flag lists, numbers, enum
instances). A value that cannot be converted, including a blank one,
is now an error named InvalidParameterValue: terminating and
catchable when it is named on the command line, exactly as the binder
reported it before; a non-terminating error targeting that path or
descriptor when it comes from a piped object, which leaves that item
unchanged and lets the pipeline continue with the next one. A $null
value is still not an error and keeps the parameter's default. Tab
completion of the five parameters keeps working through explicit
argument completers. Choosing between the Simple and Complex
parameter sets is unaffected and still rejects AppliesTo together
with InheritanceFlags or PropagationFlags as AmbiguousParameterSet,
for every mix of string and enum values.
The four cmdlet pages document the parameters as Type: Object, list
the valid values, and describe the new error; a sentence that said a
piped AppliesTo typo or blank cell was silently ignored is corrected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
# A blank cell that Import-Csv reads is an empty string, not $null. Since -AppliesTo is now an object
# parameter (Decision 27, fix B), the blank string still binds it, which selects PathSimple, so the row gets
# an InvalidParameterValue error instead of the default scope (ParameterSets.Access.Tests.ps1 has the same rows).
It 'Should write an InvalidParameterValue error for the row of a CSV file whose AppliesTo cell is blank, leave its folder alone, and still give the other rows their own scope' -Skip : ( -not $canReadAudit ) {
docs(access,audit): say what an empty or null AppliesTo does, and test the Import-Csv route
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$folders = @ ( 'AddCsvAuditBlankFirst' , 'AddCsvAuditBlankCell' , 'AddCsvAuditBlankLast' |
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
ForEach-Object -Process { New-TestSandboxItem -Sandbox $sandbox -Name $_ -Directory } )
docs(access,audit): say what an empty or null AppliesTo does, and test the Import-Csv route
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$csv = Join-Path -Path $sandbox -ChildPath 'AddCsvAuditBlank.csv'
Assert-TestSandboxPath -Sandbox $sandbox -Path $csv
Set-Content -LiteralPath $csv -Value @ (
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
'FullName,Account,AccessRights,AppliesTo'
( '"{0}",{1},ReadData,ThisFolderOnly' -f $folders [ 0 ] , $probe )
( '"{0}",{1},ReadData,' -f $folders [ 1 ] , $probe )
( '"{0}",{1},ReadData,SubfoldersAndFilesOnly' -f $folders [ 2 ] , $probe )
docs(access,audit): say what an empty or null AppliesTo does, and test the Import-Csv route
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
)
fix(access): report a piped AccessType, AuditFlags, flags, or scope that isn't valid
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
silently kept the default value of AccessType, AuditFlags,
InheritanceFlags, PropagationFlags, and AppliesTo when a piped object's
property could not be converted to the parameter's type: a misspelled
AccessType such as "Denyy", or a blank CSV cell, created an Allow entry
with no message. For a permissions tool, a typo for Deny that silently
grants access is a security-relevant defect (Decision 27).
The five parameters are now declared as object and converted inside
the cmdlet, the way the PowerShell binder converts a typed parameter
(names in any case, comma-separated flag lists, numbers, enum
instances). A value that cannot be converted, including a blank one,
is now an error named InvalidParameterValue: terminating and
catchable when it is named on the command line, exactly as the binder
reported it before; a non-terminating error targeting that path or
descriptor when it comes from a piped object, which leaves that item
unchanged and lets the pipeline continue with the next one. A $null
value is still not an error and keeps the parameter's default. Tab
completion of the five parameters keeps working through explicit
argument completers. Choosing between the Simple and Complex
parameter sets is unaffected and still rejects AppliesTo together
with InheritanceFlags or PropagationFlags as AmbiguousParameterSet,
for every mix of string and enum values.
The four cmdlet pages document the parameters as Type: Object, list
the valid values, and describe the new error; a sentence that said a
piped AppliesTo typo or blank cell was silently ignored is corrected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
Import-Csv -LiteralPath $csv | Add-NTFSAudit -ErrorVariable auditErrors -ErrorAction SilentlyContinue
docs(access,audit): say what an empty or null AppliesTo does, and test the Import-Csv route
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
fix(access): report a piped AccessType, AuditFlags, flags, or scope that isn't valid
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
silently kept the default value of AccessType, AuditFlags,
InheritanceFlags, PropagationFlags, and AppliesTo when a piped object's
property could not be converted to the parameter's type: a misspelled
AccessType such as "Denyy", or a blank CSV cell, created an Allow entry
with no message. For a permissions tool, a typo for Deny that silently
grants access is a security-relevant defect (Decision 27).
The five parameters are now declared as object and converted inside
the cmdlet, the way the PowerShell binder converts a typed parameter
(names in any case, comma-separated flag lists, numbers, enum
instances). A value that cannot be converted, including a blank one,
is now an error named InvalidParameterValue: terminating and
catchable when it is named on the command line, exactly as the binder
reported it before; a non-terminating error targeting that path or
descriptor when it comes from a piped object, which leaves that item
unchanged and lets the pipeline continue with the next one. A $null
value is still not an error and keeps the parameter's default. Tab
completion of the five parameters keeps working through explicit
argument completers. Choosing between the Simple and Complex
parameter sets is unaffected and still rejects AppliesTo together
with InheritanceFlags or PropagationFlags as AmbiguousParameterSet,
for every mix of string and enum values.
The four cmdlet pages document the parameters as Type: Object, list
the valid values, and describe the new error; a sentence that said a
piped AppliesTo typo or blank cell was silently ignored is corrected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
$auditErrors | Should -HaveCount 1
$auditErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'InvalidParameterValue,*'
$auditErrors [ 0 ] . CategoryInfo . Category | Should -Be 'InvalidArgument'
$auditErrors [ 0 ] . TargetObject | Should -Be $folders [ 1 ]
docs(access,audit): say what an empty or null AppliesTo does, and test the Import-Csv route
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$scopes = $folders | ForEach-Object -Process { Get-TestAuditScope -Acl ( Get-Acl -LiteralPath $_ -Audit ) }
fix(access): report a piped AccessType, AuditFlags, flags, or scope that isn't valid
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
silently kept the default value of AccessType, AuditFlags,
InheritanceFlags, PropagationFlags, and AppliesTo when a piped object's
property could not be converted to the parameter's type: a misspelled
AccessType such as "Denyy", or a blank CSV cell, created an Allow entry
with no message. For a permissions tool, a typo for Deny that silently
grants access is a security-relevant defect (Decision 27).
The five parameters are now declared as object and converted inside
the cmdlet, the way the PowerShell binder converts a typed parameter
(names in any case, comma-separated flag lists, numbers, enum
instances). A value that cannot be converted, including a blank one,
is now an error named InvalidParameterValue: terminating and
catchable when it is named on the command line, exactly as the binder
reported it before; a non-terminating error targeting that path or
descriptor when it comes from a piped object, which leaves that item
unchanged and lets the pipeline continue with the next one. A $null
value is still not an error and keeps the parameter's default. Tab
completion of the five parameters keeps working through explicit
argument completers. Choosing between the Simple and Complex
parameter sets is unaffected and still rejects AppliesTo together
with InheritanceFlags or PropagationFlags as AmbiguousParameterSet,
for every mix of string and enum values.
The four cmdlet pages document the parameters as Type: Object, list
the valid values, and describe the new error; a sentence that said a
piped AppliesTo typo or blank cell was silently ignored is corrected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
$scopes -join '|' | Should -BeExactly 'None/None||ContainerInherit, ObjectInherit/InheritOnly'
docs(access,audit): say what an empty or null AppliesTo does, and test the Import-Csv route
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
}
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
docs(access,audit): say what an empty or null AppliesTo does, and test the Import-Csv route
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
# $null cannot be bound at all: the object fails, loudly, and the cmdlet goes on with the next one.
It 'Should report a binding error for the piped object whose AppliesTo is $null, leave its folder alone, and still process the other objects' -Skip : ( -not $canReadAudit ) {
$folders = @ ( 'AddNullAuditFirst' , 'AddNullAuditMiddle' , 'AddNullAuditLast' |
ForEach-Object -Process { New-TestSandboxItem -Sandbox $sandbox -Name $_ -Directory } )
@ (
[pscustomobject] ( $entry + @ { FullName = $folders [ 0 ] ; AppliesTo = 'ThisFolderOnly' } )
[pscustomobject] ( $entry + @ { FullName = $folders [ 1 ] ; AppliesTo = $null } )
[pscustomobject] ( $entry + @ { FullName = $folders [ 2 ] ; AppliesTo = 'SubfoldersAndFilesOnly' } )
) | Add-NTFSAudit -ErrorVariable auditErrors -ErrorAction SilentlyContinue
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
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
docs(access,audit): say what an empty or null AppliesTo does, and test the Import-Csv route
The follow-up review of 9783d2a found that "an empty AppliesTo fails to
bind", in the changelog entry and in the four cmdlet pages, holds only for
$null. A blank cell that Import-Csv reads is an empty string, which the
binder cannot convert: PathSimple does not bind, and the row gets the
default scope, as an object without the property does. An AppliesTo that
is not the name of a scope is ignored the same way (probe
p-empty-appliesto.ps1, both editions, on the package of 9783d2a and on
the baseline). Only $null fails to bind, with
ParameterArgumentValidationErrorNullNotAllowed, for that object.
The pages, the help file, and the changelog entry now say so.
The tests of the CSV route read a CSV file with Import-Csv, the route
that the documentation shows, instead of ConvertFrom-Csv of strings,
which makes a trailing blank cell $null. They pin the blank cell as the
default scope and the $null object as a binding error for that object
only, with the other objects processed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$auditErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ParameterArgumentValidationErrorNullNotAllowed,*'
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$scopes = $folders | ForEach-Object -Process { Get-TestAuditScope -Acl ( Get-Acl -LiteralPath $_ -Audit ) }
$scopes -join '|' | Should -BeExactly 'None/None||ContainerInherit, ObjectInherit/InheritOnly'
}
It 'Should give each piped descriptor object only its own scope: AppliesTo, then none' -Skip : ( -not $canReadAudit ) {
$folders = @ ( 'AddMixedAuditSDThisFolderOnly' , 'AddMixedAuditSDNoScope' |
ForEach-Object -Process { New-TestSandboxItem -Sandbox $sandbox -Name $_ -Directory } )
$descriptors = @ ( Get-NTFSSecurityDescriptor -Path $folders -ErrorAction Stop )
@ (
[pscustomobject] ( $entry + @ { SecurityDescriptor = $descriptors [ 0 ] ; AppliesTo = 'ThisFolderOnly' } )
[pscustomobject] ( $entry + @ { SecurityDescriptor = $descriptors [ 1 ] } )
) | Add-NTFSAudit -ErrorAction Stop
$scopes = $descriptors | ForEach-Object -Process { Get-TestAuditScope -Acl $_ . SecurityDescriptor }
$scopes -join '|' | Should -BeExactly 'None/None|ContainerInherit, ObjectInherit/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
Context 'Conflicting parameters' {
It 'Should reject -AppliesTo combined with -InheritanceFlags with an AmbiguousParameterSet error' {
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
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'AddAmbiguous'
{ Add-NTFSAudit -Path $file -Account $probe -AccessRights ReadData -AppliesTo ThisFolderOnly -InheritanceFlags None -ErrorAction Stop } |
Should -Throw -ErrorId 'AmbiguousParameterSet,NTFSSecurity.AddAudit'
}
}
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
}
}
fix(access): report a failing change of a descriptor as an error per descriptor
In the four -SecurityDescriptor parameter sets of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit, a change that
throws, such as a Deny entry without rights, ended the cmdlet with a
terminating error, while the -Path parameter sets already wrote an
AddAceError or RemoveAceError for the failing item and went on to the
next one. The two kinds of parameter sets now behave the same way.
Each descriptor's change is wrapped in its own try/catch: a failure
writes a non-terminating AddAceError or RemoveAceError, category
WriteError, that targets the descriptor, and the cmdlet continues
with the next descriptor in the pipeline instead of stopping. -PassThru
stays outside the try, so what a later command raises when the
cmdlet writes its output is never caught there. The existing
TestAuditSection check of the audit cmdlets, and everything else
about a successful change, is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
# Item 5 of Decision 27: a failing change to one of several descriptors (an audit entry without rights, which
# .NET refuses to build, the same as Audit.Tests.ps1:141 pins for -Path) reports an error for that descriptor and
# goes on with the next one, instead of ending the cmdlet with a terminating error.
Context 'A failing change to one of several descriptors' {
It 'Should write an AddAceError for the descriptor whose change fails in the SDSimple set, continue with the next, and return -PassThru only for the one that succeeded' -Skip : ( -not $canReadAudit ) {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'SdErrorSimpleFirst'
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'SdErrorSimpleSecond'
$descriptors = @ ( Get-NTFSSecurityDescriptor -Path $first , $second -ErrorAction Stop )
$before = $descriptors [ 0 ] . SecurityDescriptor . GetSecurityDescriptorSddlForm ( 'Audit' )
$rows = @ (
[pscustomobject] @ { SecurityDescriptor = $descriptors [ 0 ] ; Account = $probe ; AccessRights = 'None' ; AuditFlags = 'Success' ; AppliesTo = 'ThisFolderOnly' }
[pscustomobject] @ { SecurityDescriptor = $descriptors [ 1 ] ; Account = $probe ; AccessRights = 'ReadData' ; AuditFlags = 'Success' ; AppliesTo = 'ThisFolderOnly' }
)
$result = @ ( $rows | Add-NTFSAudit -PassThru -ErrorVariable sdErrors -ErrorAction SilentlyContinue )
$sdErrors | Should -HaveCount 1
$sdErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'AddAceError,*'
$sdErrors [ 0 ] . CategoryInfo . Category | Should -Be 'WriteError'
$sdErrors [ 0 ] . TargetObject | Should -Be $descriptors [ 0 ]
$descriptors [ 0 ] . SecurityDescriptor . GetSecurityDescriptorSddlForm ( 'Audit' ) | Should -BeExactly $before
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_ . FullName | Should -Be $second }
}
It 'Should write an AddAceError for the descriptor whose change fails in the SDComplex set, continue with the next, and return -PassThru only for the one that succeeded' -Skip : ( -not $canReadAudit ) {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'SdErrorComplexFirst'
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'SdErrorComplexSecond'
$descriptors = @ ( Get-NTFSSecurityDescriptor -Path $first , $second -ErrorAction Stop )
$before = $descriptors [ 0 ] . SecurityDescriptor . GetSecurityDescriptorSddlForm ( 'Audit' )
$rows = @ (
[pscustomobject] @ { SecurityDescriptor = $descriptors [ 0 ] ; Account = $probe ; AccessRights = 'None' ; AuditFlags = 'Success' }
[pscustomobject] @ { SecurityDescriptor = $descriptors [ 1 ] ; Account = $probe ; AccessRights = 'ReadData' ; AuditFlags = 'Success' }
)
$result = @ ( $rows | Add-NTFSAudit -PassThru -ErrorVariable sdErrors -ErrorAction SilentlyContinue )
$sdErrors | Should -HaveCount 1
$sdErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'AddAceError,*'
$sdErrors [ 0 ] . CategoryInfo . Category | Should -Be 'WriteError'
$sdErrors [ 0 ] . TargetObject | Should -Be $descriptors [ 0 ]
$descriptors [ 0 ] . SecurityDescriptor . GetSecurityDescriptorSddlForm ( 'Audit' ) | Should -BeExactly $before
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_ . FullName | Should -Be $second }
}
}
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
}
Describe 'Remove-NTFSAudit' {
Context 'Binding -Path from the pipeline by property name' {
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
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 and the PathSimpleNamed row names it. Neither can tell the sets apart here: the
# entry is on a file, where the scope does not matter. The Contexts on folders below do.
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
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
Get-TestAuditScope -Acl ( Get-Acl -LiteralPath $folder -Audit ) | Should -BeExactly $Left
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
}
It 'Should reject a piped object that has both AppliesTo and InheritanceFlags with an AmbiguousParameterSet error' {
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
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'RemovePipedAuditBoth' -Directory
$object = [pscustomobject] @ { FullName = $folder ; Account = $probe ; AccessRights = 'ReadData' ; AppliesTo = 'ThisFolderOnly' ; InheritanceFlags = 'None' }
{ $object | Remove-NTFSAudit -ErrorAction Stop } | Should -Throw -ErrorId 'AmbiguousParameterSet*'
}
}
fix(access,audit): keep the scope of a piped object from reaching the next one
Independent review of 93ed2c7: ProcessRecord of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit wrote the scope
that it converts from AppliesTo into the fields behind InheritanceFlags
and PropagationFlags. PowerShell resets only the parameters that the
previous piped object bound, so the next object that had no scope, or
only one flag, took the scope of the one before it: AppliesTo
ThisFolderOnly, then none gave None/None twice, and Remove left the
InheritOnly part of every entry. Without the default set (93ed2c7) a
piped path object with AppliesTo reaches that code; for a piped
-SecurityDescriptor object it was a defect of rc7 already.
The flags of a record are locals now. The loop of the two Remove
cmdlets converted the scope a second time into the same fields; the
locals make that redundant, so it is gone.
The tests pipe objects with and without a scope, a CSV file, and
descriptor objects into the four cmdlets: red on the build of 93ed2c7
(8 of 87 cases of the two files, in every elevated configuration; 4 in
the basic-user ones, where the audit rows skip), green on this build.
An empty AppliesTo cell fails to bind for that row only; a test pins it.
The Remove-NTFSAudit PathSimple row cannot tell the sets apart (its
entry is on a file), and its comment says so. Docs/Examples.md no longer
says that a scope is needed so that PowerShell can choose the parameter
set (true before 5.0.0 only); the changelog entry names the new errors.
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 scope that the cmdlet derives from AppliesTo must not stay for the next object.
Context 'Objects with different scopes in one pipeline' {
It 'Should remove from each piped path object the entry of its own scope: AppliesTo, none, AppliesTo, none' -Skip : ( -not $canReadAudit ) {
$folders = @ ( 'RemoveMixedAuditFirst' , 'RemoveMixedAuditSecond' , 'RemoveMixedAuditThird' , 'RemoveMixedAuditFourth' | ForEach-Object -Process {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name $_ -Directory
Add-NTFSAudit -Path $folder @entry -ErrorAction Stop
$folder
} )
@ (
[pscustomobject] ( $entry + @ { FullName = $folders [ 0 ] ; AppliesTo = 'ThisFolderOnly' } )
[pscustomobject] ( $entry + @ { FullName = $folders [ 1 ] } )
[pscustomobject] ( $entry + @ { FullName = $folders [ 2 ] ; AppliesTo = 'ThisFolderOnly' } )
[pscustomobject] ( $entry + @ { FullName = $folders [ 3 ] } )
) | Remove-NTFSAudit -ErrorAction Stop
$remaining = $folders | ForEach-Object -Process { Get-TestAuditScope -Acl ( Get-Acl -LiteralPath $_ -Audit ) }
$remaining -join '|' | Should -BeExactly 'ContainerInherit, ObjectInherit/InheritOnly||ContainerInherit, ObjectInherit/InheritOnly|'
}
It 'Should remove from each piped descriptor object the entry of its own scope: AppliesTo, then none' -Skip : ( -not $canReadAudit ) {
$folders = @ ( 'RemoveMixedAuditSDFirst' , 'RemoveMixedAuditSDSecond' | ForEach-Object -Process {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name $_ -Directory
Add-NTFSAudit -Path $folder @entry -ErrorAction Stop
$folder
} )
$descriptors = @ ( Get-NTFSSecurityDescriptor -Path $folders -ErrorAction Stop )
@ (
[pscustomobject] ( $entry + @ { SecurityDescriptor = $descriptors [ 0 ] ; AppliesTo = 'ThisFolderOnly' } )
[pscustomobject] ( $entry + @ { SecurityDescriptor = $descriptors [ 1 ] } )
) | Remove-NTFSAudit -ErrorAction Stop
$remaining = $descriptors | ForEach-Object -Process { Get-TestAuditScope -Acl $_ . SecurityDescriptor }
$remaining -join '|' | Should -BeExactly 'ContainerInherit, ObjectInherit/InheritOnly|'
}
}
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
}
}
test(audit): close budget gaps for Add/Remove/Clear/Get-NTFSAudit and Get-NTFSOrphanedAudit
Closes the eight remaining 'budget' parameter-set gaps that
Quality-Gate-Unknowns-2026-10-10-ParameterSets.csv lists for the audit
cmdlets, in Tests\ParameterSets.Audit.Tests.ps1 only:
- Add-NTFSAudit PathComplex: binds -Account (several accounts) and
-AuditFlags from a single piped object's property names in one call.
- Remove-NTFSAudit PathComplex: removes for several accounts named in one
call, and leaves the empty Audit SDDL and the DACL unchanged for an item
that has no SACL.
- Remove-NTFSAudit SDSimple and SDComplex: proves the item on disk keeps
its audit section until Set-NTFSSecurityDescriptor writes the change,
and that -PassThru returns FileSystemAuditRule2 objects for both sets.
- Clear-NTFSAudit Path: asserts the cleared state of the succeeding path
that follows a missing one in the same pipeline.
- Get-NTFSAudit SD: asserts the output's type, account, rights, flags, and
inherited state directly, independent of the Path set's output.
- Get-NTFSOrphanedAudit Path: adds the OpenError category of the
ReadSecurityError without the Security privilege (PermissionDenied needs
a remote/lab target and is not in scope), and -ExcludeExplicit.
- Get-NTFSOrphanedAudit SD: adds -Account and -ExcludeInherited/
-ExcludeExplicit filters against a piped security descriptor.
Every new test reads state back with an independent oracle (Get-Acl
-Audit or the .NET SACL-only accessor), and the Set-NTFSSecurityDescriptor
cases confirm the SD cmdlets change the item only in memory. The owner
retry after access denied (Limit) and the open maintainer item on
exceptions from the SD change itself (MaintainerDecision) are left out, as
the worklist directs.
9 new It blocks (10 executed cases; one is split SDSimple/SDComplex by
-ForEach) were verified test-first: a mutated copy with one flipped
expectation per new It failed only those 10 cases in the configurations
where they run, and nothing else; three repeated runs of the real file in
all four configurations (Windows PowerShell 5.1 and PowerShell 7, elevated
and basic user) gave identical, all-green results; and
Invoke-ScriptAnalyzer reports nothing, matching the base commit's file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
Context 'Removing for several accounts in one call' {
# judged-audit.csv (Remove-NTFSAudit PathComplex): no existing call removes for several accounts in one call.
It 'Should remove the audit entry of each account that -Account names in one call, and leave another account alone' -Skip : ( -not $canReadAudit ) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSeveralAccounts'
$bystander = 'S-1-5-11'
foreach ( $account in $probe , $keeper , $bystander ) {
Add-NTFSAudit -Path $file -Account $account -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
}
$daclBefore = Get-TestDaclSddl -Path $file
$result = @ ( Remove-NTFSAudit -Path $file -Account $probe , $keeper -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -PassThru -ErrorAction Stop )
$acl = Get-Acl -LiteralPath $file -Audit
( Get-TestAccountAuditRule -Acl $acl -Account $probe ) | Should -BeNullOrEmpty
( Get-TestAccountAuditRule -Acl $acl -Account $keeper ) | Should -BeNullOrEmpty
( Get-TestAccountAuditRule -Acl $acl -Account $bystander ) | Should -HaveCount 1
$result | Should -HaveCount 1
$result [ 0 ] . Account . Sid | Should -Be $bystander
Get-TestDaclSddl -Path $file | Should -BeExactly $daclBefore
}
}
Context 'When the item has no SACL' {
# judged-audit.csv (Remove-NTFSAudit PathComplex): Audit.Tests.ps1:406 asserts no error only; this adds the
# empty Audit SDDL and the unchanged DACL after the call.
It 'Should leave the empty Audit SDDL and the DACL unchanged' -Skip : ( -not $canReadAudit ) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveNoSaclComplex'
Get-TestAuditSddl -Path $file | Should -BeNullOrEmpty
$daclBefore = Get-TestDaclSddl -Path $file
Remove-NTFSAudit -Path $file -Account $probe -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -ErrorVariable removeErrors -ErrorAction SilentlyContinue
$removeErrors | Should -BeNullOrEmpty
Get-TestAuditSddl -Path $file | Should -BeNullOrEmpty
Get-TestDaclSddl -Path $file | Should -BeExactly $daclBefore
}
}
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 '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
}
}
test(audit): close budget gaps for Add/Remove/Clear/Get-NTFSAudit and Get-NTFSOrphanedAudit
Closes the eight remaining 'budget' parameter-set gaps that
Quality-Gate-Unknowns-2026-10-10-ParameterSets.csv lists for the audit
cmdlets, in Tests\ParameterSets.Audit.Tests.ps1 only:
- Add-NTFSAudit PathComplex: binds -Account (several accounts) and
-AuditFlags from a single piped object's property names in one call.
- Remove-NTFSAudit PathComplex: removes for several accounts named in one
call, and leaves the empty Audit SDDL and the DACL unchanged for an item
that has no SACL.
- Remove-NTFSAudit SDSimple and SDComplex: proves the item on disk keeps
its audit section until Set-NTFSSecurityDescriptor writes the change,
and that -PassThru returns FileSystemAuditRule2 objects for both sets.
- Clear-NTFSAudit Path: asserts the cleared state of the succeeding path
that follows a missing one in the same pipeline.
- Get-NTFSAudit SD: asserts the output's type, account, rights, flags, and
inherited state directly, independent of the Path set's output.
- Get-NTFSOrphanedAudit Path: adds the OpenError category of the
ReadSecurityError without the Security privilege (PermissionDenied needs
a remote/lab target and is not in scope), and -ExcludeExplicit.
- Get-NTFSOrphanedAudit SD: adds -Account and -ExcludeInherited/
-ExcludeExplicit filters against a piped security descriptor.
Every new test reads state back with an independent oracle (Get-Acl
-Audit or the .NET SACL-only accessor), and the Set-NTFSSecurityDescriptor
cases confirm the SD cmdlets change the item only in memory. The owner
retry after access denied (Limit) and the open maintainer item on
exceptions from the SD change itself (MaintainerDecision) are left out, as
the worklist directs.
9 new It blocks (10 executed cases; one is split SDSimple/SDComplex by
-ForEach) were verified test-first: a mutated copy with one flipped
expectation per new It failed only those 10 cases in the configurations
where they run, and nothing else; three repeated runs of the real file in
all four configurations (Windows PowerShell 5.1 and PowerShell 7, elevated
and basic user) gave identical, all-green results; and
Invoke-ScriptAnalyzer reports nothing, matching the base commit's file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
Context 'Leaving the item on disk untouched until Set-NTFSSecurityDescriptor writes the change' {
# judged-audit.csv (Remove-NTFSAudit SDSimple and SDComplex): the pipeline test above (binding
# -SecurityDescriptor by property name) proves only that the in-memory descriptor lost the account's rule;
# this proves the item on disk keeps its audit section until Set-NTFSSecurityDescriptor writes it, and that
# -PassThru returns audit rule objects for the SDComplex set too.
It 'Should remove the entry from the <Set> descriptor only in memory, until Set-NTFSSecurityDescriptor writes it, and return audit rule objects with -PassThru' -ForEach @ (
@ { Set = 'SDSimple' ; ExtraProperties = @ { AppliesTo = 'ThisFolderOnly' } }
@ { Set = 'SDComplex' ; ExtraProperties = @ { InheritanceFlags = 'None' ; PropagationFlags = 'None' } }
) -Skip : ( -not $canReadAudit ) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name " RemoveSDUntouched $ Set "
Add-NTFSAudit -Path $file @entry @ExtraProperties -ErrorAction Stop
Add-NTFSAudit -Path $file -Account $keeper -AccessRights Delete @ExtraProperties -ErrorAction Stop
$auditBefore = Get-TestAuditSddl -Path $file
$daclBefore = Get-TestDaclSddl -Path $file
$sd = Get-NTFSSecurityDescriptor -Path $file -ErrorAction Stop
$result = @ ( Remove-NTFSAudit -SecurityDescriptor $sd @entry @ExtraProperties -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-TestAuditSddl -Path $file | Should -BeExactly $auditBefore
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop
( Get-TestAccountAuditRule -Acl ( Get-TestAuditAcl -Path $file ) -Account $probe ) | Should -BeNullOrEmpty
( Get-TestAccountAuditRule -Acl ( Get-TestAuditAcl -Path $file ) -Account $keeper ) | Should -HaveCount 1
Get-TestAuditSddl -Path $file | Should -Not -BeExactly $auditBefore
Get-TestDaclSddl -Path $file | Should -BeExactly $daclBefore
}
}
fix(access): report a failing change of a descriptor as an error per descriptor
In the four -SecurityDescriptor parameter sets of Add-NTFSAccess,
Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit, a change that
throws, such as a Deny entry without rights, ended the cmdlet with a
terminating error, while the -Path parameter sets already wrote an
AddAceError or RemoveAceError for the failing item and went on to the
next one. The two kinds of parameter sets now behave the same way.
Each descriptor's change is wrapped in its own try/catch: a failure
writes a non-terminating AddAceError or RemoveAceError, category
WriteError, that targets the descriptor, and the cmdlet continues
with the next descriptor in the pipeline instead of stopping. -PassThru
stays outside the try, so what a later command raises when the
cmdlet writes its output is never caught there. The existing
TestAuditSection check of the audit cmdlets, and everything else
about a successful change, is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
# Item 5 of Decision 27: a failing change to one of several descriptors (an audit entry without rights, which
# .NET refuses to build, the same as Audit.Tests.ps1:141 pins for -Path) reports an error for that descriptor and
# goes on with the next one, instead of ending the cmdlet with a terminating error. Only the SDComplex set is
# listed as open in Quality-Gate-Test-Round-2026-10-20-ParameterSets.csv (row 26); SDSimple is already closed.
Context 'A failing change to one of several descriptors' {
It 'Should write a RemoveAceError for the descriptor whose change fails in the SDComplex set, continue with the next, and return -PassThru only for the one that succeeded' -Skip : ( -not $canReadAudit ) {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'SdErrorComplexFirst'
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'SdErrorComplexSecond'
# A keeper entry for a different account on both items: audit entries are not inherited by default, so
# without one, -PassThru of the descriptor that succeeds would show nothing once its only entry is gone.
Add-NTFSAudit -Path $first , $second -Account $probe -AccessRights ReadData -AuditFlags Success -ErrorAction Stop
Add-NTFSAudit -Path $first , $second -Account $keeper -AccessRights Delete -AuditFlags Success -ErrorAction Stop
$descriptors = @ ( Get-NTFSSecurityDescriptor -Path $first , $second -ErrorAction Stop )
$before = $descriptors [ 0 ] . SecurityDescriptor . GetSecurityDescriptorSddlForm ( 'Audit' )
$rows = @ (
[pscustomobject] @ { SecurityDescriptor = $descriptors [ 0 ] ; Account = $probe ; AccessRights = 'None' ; AuditFlags = 'Success' }
[pscustomobject] @ { SecurityDescriptor = $descriptors [ 1 ] ; Account = $probe ; AccessRights = 'ReadData' ; AuditFlags = 'Success' }
)
$result = @ ( $rows | Remove-NTFSAudit -PassThru -ErrorVariable sdErrors -ErrorAction SilentlyContinue )
$sdErrors | Should -HaveCount 1
$sdErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'RemoveAceError,*'
$sdErrors [ 0 ] . CategoryInfo . Category | Should -Be 'WriteError'
$sdErrors [ 0 ] . TargetObject | Should -Be $descriptors [ 0 ]
$descriptors [ 0 ] . SecurityDescriptor . GetSecurityDescriptorSddlForm ( 'Audit' ) | Should -BeExactly $before
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_ . FullName | Should -Be $second }
}
}
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
}
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 ]
}
test(audit): close budget gaps for Add/Remove/Clear/Get-NTFSAudit and Get-NTFSOrphanedAudit
Closes the eight remaining 'budget' parameter-set gaps that
Quality-Gate-Unknowns-2026-10-10-ParameterSets.csv lists for the audit
cmdlets, in Tests\ParameterSets.Audit.Tests.ps1 only:
- Add-NTFSAudit PathComplex: binds -Account (several accounts) and
-AuditFlags from a single piped object's property names in one call.
- Remove-NTFSAudit PathComplex: removes for several accounts named in one
call, and leaves the empty Audit SDDL and the DACL unchanged for an item
that has no SACL.
- Remove-NTFSAudit SDSimple and SDComplex: proves the item on disk keeps
its audit section until Set-NTFSSecurityDescriptor writes the change,
and that -PassThru returns FileSystemAuditRule2 objects for both sets.
- Clear-NTFSAudit Path: asserts the cleared state of the succeeding path
that follows a missing one in the same pipeline.
- Get-NTFSAudit SD: asserts the output's type, account, rights, flags, and
inherited state directly, independent of the Path set's output.
- Get-NTFSOrphanedAudit Path: adds the OpenError category of the
ReadSecurityError without the Security privilege (PermissionDenied needs
a remote/lab target and is not in scope), and -ExcludeExplicit.
- Get-NTFSOrphanedAudit SD: adds -Account and -ExcludeInherited/
-ExcludeExplicit filters against a piped security descriptor.
Every new test reads state back with an independent oracle (Get-Acl
-Audit or the .NET SACL-only accessor), and the Set-NTFSSecurityDescriptor
cases confirm the SD cmdlets change the item only in memory. The owner
retry after access denied (Limit) and the open maintainer item on
exceptions from the SD change itself (MaintainerDecision) are left out, as
the worklist directs.
9 new It blocks (10 executed cases; one is split SDSimple/SDComplex by
-ForEach) were verified test-first: a mutated copy with one flipped
expectation per new It failed only those 10 cases in the configurations
where they run, and nothing else; three repeated runs of the real file in
all four configurations (Windows PowerShell 5.1 and PowerShell 7, elevated
and basic user) gave identical, all-green results; and
Invoke-ScriptAnalyzer reports nothing, matching the base commit's file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
# judged-audit.csv (Clear-NTFSAudit Path): no existing test pipes a failing path and asserts the result of
# the succeeding path that follows it.
It 'Should clear the audit entries of the path that follows a missing one, and write only a ReadFileError for the missing one' -Skip : ( -not $canReadAudit ) {
$missing = Join-Path -Path $sandbox -ChildPath ( 'ClearPipeMissing-{0}' -f [guid] :: NewGuid ( ) . ToString ( 'N' ) )
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing
$good = New-TestSandboxItem -Sandbox $sandbox -Name 'ClearPipeGoodAfterMissing'
Add-NTFSAudit -Path $good @entry -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$daclBefore = Get-TestDaclSddl -Path $good
$missing , $good | Clear-NTFSAudit -ErrorVariable clearErrors -ErrorAction SilentlyContinue
$clearErrors | Should -HaveCount 1
$clearErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
$clearErrors [ 0 ] . CategoryInfo . Category | Should -Be 'OpenError'
$clearErrors [ 0 ] . TargetObject | Should -Be $missing
@ ( Get-NTFSAudit -Path $good -ExcludeInherited ) | Should -BeNullOrEmpty
Get-TestDaclSddl -Path $good | Should -BeExactly $daclBefore
}
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 '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 ';' )
}
}
test(audit): close budget gaps for Add/Remove/Clear/Get-NTFSAudit and Get-NTFSOrphanedAudit
Closes the eight remaining 'budget' parameter-set gaps that
Quality-Gate-Unknowns-2026-10-10-ParameterSets.csv lists for the audit
cmdlets, in Tests\ParameterSets.Audit.Tests.ps1 only:
- Add-NTFSAudit PathComplex: binds -Account (several accounts) and
-AuditFlags from a single piped object's property names in one call.
- Remove-NTFSAudit PathComplex: removes for several accounts named in one
call, and leaves the empty Audit SDDL and the DACL unchanged for an item
that has no SACL.
- Remove-NTFSAudit SDSimple and SDComplex: proves the item on disk keeps
its audit section until Set-NTFSSecurityDescriptor writes the change,
and that -PassThru returns FileSystemAuditRule2 objects for both sets.
- Clear-NTFSAudit Path: asserts the cleared state of the succeeding path
that follows a missing one in the same pipeline.
- Get-NTFSAudit SD: asserts the output's type, account, rights, flags, and
inherited state directly, independent of the Path set's output.
- Get-NTFSOrphanedAudit Path: adds the OpenError category of the
ReadSecurityError without the Security privilege (PermissionDenied needs
a remote/lab target and is not in scope), and -ExcludeExplicit.
- Get-NTFSOrphanedAudit SD: adds -Account and -ExcludeInherited/
-ExcludeExplicit filters against a piped security descriptor.
Every new test reads state back with an independent oracle (Get-Acl
-Audit or the .NET SACL-only accessor), and the Set-NTFSSecurityDescriptor
cases confirm the SD cmdlets change the item only in memory. The owner
retry after access denied (Limit) and the open maintainer item on
exceptions from the SD change itself (MaintainerDecision) are left out, as
the worklist directs.
9 new It blocks (10 executed cases; one is split SDSimple/SDComplex by
-ForEach) were verified test-first: a mutated copy with one flipped
expectation per new It failed only those 10 cases in the configurations
where they run, and nothing else; three repeated runs of the real file in
all four configurations (Windows PowerShell 5.1 and PowerShell 7, elevated
and basic user) gave identical, all-green results; and
Invoke-ScriptAnalyzer reports nothing, matching the base commit's file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
Context 'Output of a security descriptor, independent of the Path set' {
# judged-audit.csv (Get-NTFSAudit SD): the pipeline test above compares the SD set's output with the Path
# set's output; this asserts concrete values instead.
It 'Should return the entries of a security descriptor with the expected type, account, rights, flags, and inherited state' -Skip : ( -not $canReadAudit ) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'GetAuditSDValues' -Directory
Add-NTFSAudit -Path $folder -Account $probe -AccessRights ReadData -AuditFlags Failure -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$sd = Get-NTFSSecurityDescriptor -Path $folder -ErrorAction Stop
$result = @ ( Get-NTFSAudit -SecurityDescriptor $sd -ErrorAction Stop )
$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] :: Failure )
$result [ 0 ] . IsInherited | Should -BeFalse
$result [ 0 ] . InheritanceEnabled | 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
}
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'
}
}
test(audit): close budget gaps for Add/Remove/Clear/Get-NTFSAudit and Get-NTFSOrphanedAudit
Closes the eight remaining 'budget' parameter-set gaps that
Quality-Gate-Unknowns-2026-10-10-ParameterSets.csv lists for the audit
cmdlets, in Tests\ParameterSets.Audit.Tests.ps1 only:
- Add-NTFSAudit PathComplex: binds -Account (several accounts) and
-AuditFlags from a single piped object's property names in one call.
- Remove-NTFSAudit PathComplex: removes for several accounts named in one
call, and leaves the empty Audit SDDL and the DACL unchanged for an item
that has no SACL.
- Remove-NTFSAudit SDSimple and SDComplex: proves the item on disk keeps
its audit section until Set-NTFSSecurityDescriptor writes the change,
and that -PassThru returns FileSystemAuditRule2 objects for both sets.
- Clear-NTFSAudit Path: asserts the cleared state of the succeeding path
that follows a missing one in the same pipeline.
- Get-NTFSAudit SD: asserts the output's type, account, rights, flags, and
inherited state directly, independent of the Path set's output.
- Get-NTFSOrphanedAudit Path: adds the OpenError category of the
ReadSecurityError without the Security privilege (PermissionDenied needs
a remote/lab target and is not in scope), and -ExcludeExplicit.
- Get-NTFSOrphanedAudit SD: adds -Account and -ExcludeInherited/
-ExcludeExplicit filters against a piped security descriptor.
Every new test reads state back with an independent oracle (Get-Acl
-Audit or the .NET SACL-only accessor), and the Set-NTFSSecurityDescriptor
cases confirm the SD cmdlets change the item only in memory. The owner
retry after access denied (Limit) and the open maintainer item on
exceptions from the SD change itself (MaintainerDecision) are left out, as
the worklist directs.
9 new It blocks (10 executed cases; one is split SDSimple/SDComplex by
-ForEach) were verified test-first: a mutated copy with one flipped
expectation per new It failed only those 10 cases in the configurations
where they run, and nothing else; three repeated runs of the real file in
all four configurations (Windows PowerShell 5.1 and PowerShell 7, elevated
and basic user) gave identical, all-green results; and
Invoke-ScriptAnalyzer reports nothing, matching the base commit's file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
Context 'Without the Security privilege' {
# judged-audit.csv (Get-NTFSOrphanedAudit Path): Audit.Tests.ps1:304 asserts the ErrorId only; this adds
# the category. Without the privilege, .NET throws PrivilegeNotHeldException, not UnauthorizedAccessException,
# so the category is OpenError, like Get-NTFSAudit (Audit.Tests.ps1:48); PermissionDenied is a Limit (needs a
# lab or a remote server).
It 'Should write a ReadSecurityError with category OpenError and the path as target' -Skip : $canReadAudit {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedNoPrivilege'
$result = @ ( Get-NTFSOrphanedAudit -Path $file -ErrorVariable orphanedErrors -ErrorAction SilentlyContinue )
$result | Should -BeNullOrEmpty
$orphanedErrors | Should -HaveCount 1
$orphanedErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$orphanedErrors [ 0 ] . CategoryInfo . Category | Should -Be 'OpenError'
$orphanedErrors [ 0 ] . TargetObject | Should -Be $file
}
}
Context 'Filtering a path with -ExcludeExplicit' {
It 'Should return only the inherited orphaned entry with -ExcludeExplicit' -Skip : ( -not $canReadAudit ) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedExcludeExplicitFolder' -Directory
Add-NTFSAudit -Path $folder -Account 'S-1-5-21-1-2-3-9101' -AccessRights ReadData -AppliesTo ThisFolderAndFiles -ErrorAction Stop
$child = Join-Path -Path $folder -ChildPath 'Child.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $child
Set-Content -LiteralPath $child -Value 'Child'
Add-NTFSAudit -Path $child -Account 'S-1-5-21-1-2-3-9102' -AccessRights Delete -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$all = @ ( Get-NTFSOrphanedAudit -Path $child -ErrorAction Stop )
$result = @ ( Get-NTFSOrphanedAudit -Path $child -ExcludeExplicit -ErrorAction Stop )
$all | Should -HaveCount 2
$result | Should -HaveCount 1
$result [ 0 ] . Account . Sid | Should -Be 'S-1-5-21-1-2-3-9101'
$result [ 0 ] . IsInherited | 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
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
}
}
test(audit): close budget gaps for Add/Remove/Clear/Get-NTFSAudit and Get-NTFSOrphanedAudit
Closes the eight remaining 'budget' parameter-set gaps that
Quality-Gate-Unknowns-2026-10-10-ParameterSets.csv lists for the audit
cmdlets, in Tests\ParameterSets.Audit.Tests.ps1 only:
- Add-NTFSAudit PathComplex: binds -Account (several accounts) and
-AuditFlags from a single piped object's property names in one call.
- Remove-NTFSAudit PathComplex: removes for several accounts named in one
call, and leaves the empty Audit SDDL and the DACL unchanged for an item
that has no SACL.
- Remove-NTFSAudit SDSimple and SDComplex: proves the item on disk keeps
its audit section until Set-NTFSSecurityDescriptor writes the change,
and that -PassThru returns FileSystemAuditRule2 objects for both sets.
- Clear-NTFSAudit Path: asserts the cleared state of the succeeding path
that follows a missing one in the same pipeline.
- Get-NTFSAudit SD: asserts the output's type, account, rights, flags, and
inherited state directly, independent of the Path set's output.
- Get-NTFSOrphanedAudit Path: adds the OpenError category of the
ReadSecurityError without the Security privilege (PermissionDenied needs
a remote/lab target and is not in scope), and -ExcludeExplicit.
- Get-NTFSOrphanedAudit SD: adds -Account and -ExcludeInherited/
-ExcludeExplicit filters against a piped security descriptor.
Every new test reads state back with an independent oracle (Get-Acl
-Audit or the .NET SACL-only accessor), and the Set-NTFSSecurityDescriptor
cases confirm the SD cmdlets change the item only in memory. The owner
retry after access denied (Limit) and the open maintainer item on
exceptions from the SD change itself (MaintainerDecision) are left out, as
the worklist directs.
9 new It blocks (10 executed cases; one is split SDSimple/SDComplex by
-ForEach) were verified test-first: a mutated copy with one flipped
expectation per new It failed only those 10 cases in the configurations
where they run, and nothing else; three repeated runs of the real file in
all four configurations (Windows PowerShell 5.1 and PowerShell 7, elevated
and basic user) gave identical, all-green results; and
Invoke-ScriptAnalyzer reports nothing, matching the base commit's file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
Context '-Account and -Exclude filters with a descriptor' {
# judged-audit.csv (Get-NTFSOrphanedAudit SD): no existing test passes -Account or -ExcludeInherited/
# -ExcludeExplicit together with a descriptor.
It 'Should filter the orphaned entries of a piped descriptor by -Account, -ExcludeExplicit, and -ExcludeInherited' -Skip : ( -not $canReadAudit ) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedSDFilterFolder' -Directory
Add-NTFSAudit -Path $folder -Account 'S-1-5-21-1-2-3-9201' -AccessRights ReadData -AppliesTo ThisFolderAndFiles -ErrorAction Stop
$child = Join-Path -Path $folder -ChildPath 'Child.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $child
Set-Content -LiteralPath $child -Value 'Child'
Add-NTFSAudit -Path $child -Account 'S-1-5-21-1-2-3-9202' -AccessRights Delete -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
$sd = Get-NTFSSecurityDescriptor -Path $child -ErrorAction Stop
$inheritedOnly = @ ( Get-NTFSOrphanedAudit -SecurityDescriptor $sd -ExcludeExplicit -ErrorAction Stop )
$explicitOnly = @ ( Get-NTFSOrphanedAudit -SecurityDescriptor $sd -ExcludeInherited -ErrorAction Stop )
$byAccount = @ ( Get-NTFSOrphanedAudit -SecurityDescriptor $sd -Account 'S-1-5-21-1-2-3-9202' -ErrorAction Stop )
$inheritedOnly | Should -HaveCount 1
$inheritedOnly [ 0 ] . Account . Sid | Should -Be 'S-1-5-21-1-2-3-9201'
$explicitOnly | Should -HaveCount 1
$explicitOnly [ 0 ] . Account . Sid | Should -Be 'S-1-5-21-1-2-3-9202'
$byAccount | Should -HaveCount 1
$byAccount [ 0 ] . Account . Sid | Should -Be 'S-1-5-21-1-2-3-9202'
}
}
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
}
fix(access): report a piped AccessType, AuditFlags, flags, or scope that isn't valid
Add-NTFSAccess, Remove-NTFSAccess, Add-NTFSAudit, and Remove-NTFSAudit
silently kept the default value of AccessType, AuditFlags,
InheritanceFlags, PropagationFlags, and AppliesTo when a piped object's
property could not be converted to the parameter's type: a misspelled
AccessType such as "Denyy", or a blank CSV cell, created an Allow entry
with no message. For a permissions tool, a typo for Deny that silently
grants access is a security-relevant defect (Decision 27).
The five parameters are now declared as object and converted inside
the cmdlet, the way the PowerShell binder converts a typed parameter
(names in any case, comma-separated flag lists, numbers, enum
instances). A value that cannot be converted, including a blank one,
is now an error named InvalidParameterValue: terminating and
catchable when it is named on the command line, exactly as the binder
reported it before; a non-terminating error targeting that path or
descriptor when it comes from a piped object, which leaves that item
unchanged and lets the pipeline continue with the next one. A $null
value is still not an error and keeps the parameter's default. Tab
completion of the five parameters keeps working through explicit
argument completers. Choosing between the Simple and Complex
parameter sets is unaffected and still rejects AppliesTo together
with InheritanceFlags or PropagationFlags as AmbiguousParameterSet,
for every mix of string and enum values.
The four cmdlet pages document the parameters as Type: Object, list
the valid values, and describe the new error; a sentence that said a
piped AppliesTo typo or blank cell was silently ignored is corrected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
# Decision 27 (fix B): AuditFlags, InheritanceFlags, PropagationFlags, and AppliesTo of Add-NTFSAudit and
# Remove-NTFSAudit are object parameters, converted in the cmdlet with LanguagePrimitives.ConvertTo, so that a piped
# value the binder cannot convert to the enumeration is an error instead of a silently kept default. A bad value is
# caught by the conversion before the cmdlet reads or writes the SACL, so none of these tests need the Security
# privilege, unlike most of the tests above.
Describe 'Invalid and valid values of AuditFlags, InheritanceFlags, PropagationFlags, and AppliesTo' {
BeforeDiscovery {
$auditBadShapes = @ (
@ { Shape = 'a misspelled name' ; Value = 'Typoo' ; ValueText = 'Typoo' }
@ { Shape = 'a blank string' ; Value = '' ; ValueText = $null }
@ { Shape = 'a white-space string' ; Value = ' ' ; ValueText = $null }
@ { Shape = 'an undefined number' ; Value = 99 ; ValueText = '99' }
@ { Shape = 'a hashtable' ; Value = @ { Unrelated = 1 } ; ValueText = $null }
)
$auditFlagsBadShapes = @ (
@ { Shape = 'a misspelled name' ; Value = 'Failurre' ; ValueText = 'Failurre' }
@ { Shape = 'a blank string' ; Value = '' ; ValueText = $null }
@ { Shape = 'a white-space string' ; Value = ' ' ; ValueText = $null }
@ { Shape = 'an undefined number' ; Value = 99 ; ValueText = '99' }
@ { Shape = 'a hashtable' ; Value = @ { Unrelated = 1 } ; ValueText = $null }
)
# The cross product of the two cmdlets that share these parameters and every bad shape: 2 * (5 + 3 * 5) = 40 rows.
$auditInvalidParameterCases = foreach ( $cmdlet in 'Add-NTFSAudit' , 'Remove-NTFSAudit' ) {
foreach ( $shapeCase in $auditFlagsBadShapes ) {
@ { Cmdlet = $cmdlet ; Parameter = 'AuditFlags' ; Shape = $shapeCase . Shape ; Value = $shapeCase . Value ; ValueText = $shapeCase . ValueText }
}
foreach ( $parameterName in 'InheritanceFlags' , 'PropagationFlags' , 'AppliesTo' ) {
foreach ( $shapeCase in $auditBadShapes ) {
@ { Cmdlet = $cmdlet ; Parameter = $parameterName ; Shape = $shapeCase . Shape ; Value = $shapeCase . Value ; ValueText = $shapeCase . ValueText }
}
}
}
$auditSdInvalidParameterCases = foreach ( $cmdlet in 'Add-NTFSAudit' , 'Remove-NTFSAudit' ) {
@ { Cmdlet = $cmdlet ; Parameter = 'AuditFlags' ; Value = 'Failurre' ; ValueText = 'Failurre' }
@ { Cmdlet = $cmdlet ; Parameter = 'AppliesTo' ; Value = 'Typoo' ; ValueText = 'Typoo' }
}
$auditNoCarryOverCases = @ (
@ { Parameter = 'AuditFlags' ; Value = 'Failurre' }
@ { Parameter = 'InheritanceFlags' ; Value = 'Typoo' }
@ { Parameter = 'PropagationFlags' ; Value = 'Typoo' }
@ { Parameter = 'AppliesTo' ; Value = 'Typoo' }
)
$auditNamedInvalidParameterCases = foreach ( $cmdlet in 'Add-NTFSAudit' , 'Remove-NTFSAudit' ) {
@ { Cmdlet = $cmdlet ; Parameter = 'AuditFlags' ; Value = 'Failurre' }
@ { Cmdlet = $cmdlet ; Parameter = 'InheritanceFlags' ; Value = 'Typoo' }
@ { Cmdlet = $cmdlet ; Parameter = 'PropagationFlags' ; Value = 'Typoo' }
@ { Cmdlet = $cmdlet ; Parameter = 'AppliesTo' ; Value = 'Typoo' }
}
$auditValidAuditFlagsShapeCases = @ (
@ { Description = 'the exact name' ; Value = 'Failure' }
@ { Description = 'a different case' ; Value = 'failure' }
@ { Description = 'a comma list without spaces' ; Value = 'Success,Failure' }
@ { Description = 'a comma list with spaces' ; Value = 'Success, Failure' }
@ { Description = 'the enum value' ; Value = [System.Security.AccessControl.AuditFlags] :: Failure }
@ { Description = 'an integer' ; Value = 2 }
)
$auditValidFlagsShapeCases = @ (
@ { Description = 'a comma list without spaces' ; Value = 'ContainerInherit,ObjectInherit' }
@ { Description = 'a comma list with spaces' ; Value = 'ContainerInherit, ObjectInherit' }
@ { Description = 'the enum value' ; Value = [System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit' }
)
$auditAmbiguousValueCases = @ (
@ { AppliesToShape = 'a string' ; AppliesToIsEnum = $false ; FlagShape = 'a string' ; FlagIsEnum = $false }
@ { AppliesToShape = 'an enum value' ; AppliesToIsEnum = $true ; FlagShape = 'a string' ; FlagIsEnum = $false }
@ { AppliesToShape = 'a string' ; AppliesToIsEnum = $false ; FlagShape = 'an enum value' ; FlagIsEnum = $true }
@ { AppliesToShape = 'an enum value' ; AppliesToIsEnum = $true ; FlagShape = 'an enum value' ; FlagIsEnum = $true }
)
$auditCompleterCases = foreach ( $cmdlet in 'Add-NTFSAudit' , 'Remove-NTFSAudit' ) {
@ { Cmdlet = $cmdlet ; Parameter = 'AuditFlags' ; EnumTypeName = 'System.Security.AccessControl.AuditFlags' }
@ { Cmdlet = $cmdlet ; Parameter = 'InheritanceFlags' ; EnumTypeName = 'System.Security.AccessControl.InheritanceFlags' }
@ { Cmdlet = $cmdlet ; Parameter = 'PropagationFlags' ; EnumTypeName = 'System.Security.AccessControl.PropagationFlags' }
@ { Cmdlet = $cmdlet ; Parameter = 'AppliesTo' ; EnumTypeName = 'Security2.ApplyTo' }
}
}
Context 'A bad value bound from the pipeline' {
# Writing an audit entry needs the Security privilege, so without it the "good" object right next to the bad
# one fails too, with its own (unrelated) privilege error; that part of the test runs only with the privilege,
# the same way most of Audit.Tests.ps1 does. The conversion failure that this test pins happens before the
# cmdlet ever touches the SACL, so the core assertions below run in every configuration.
It 'Should write one InvalidParameterValue error for <Cmdlet> -<Parameter> (<Shape>), change nothing, and still process the next object' -ForEach $auditInvalidParameterCases {
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'InvalidValue' -Directory
$next = New-TestSandboxItem -Sandbox $sandbox -Name 'InvalidValueNext' -Directory
$before = if ( $canReadAudit ) { Get-TestAuditSddl -Path $path }
$extra = @ { }
$extra [ $Parameter ] = $Value
$rows = @ (
[pscustomobject] ( @ { FullName = $path ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' } + $extra )
[pscustomobject] @ { FullName = $next ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' }
)
$result = @ ( $rows | & $Cmdlet -PassThru -ErrorVariable pipelineErrors -ErrorAction SilentlyContinue )
$invalidValueErrors = @ ( $pipelineErrors | Where-Object -FilterScript { $_ . TargetObject -eq $path } )
$invalidValueErrors | Should -HaveCount 1
$invalidValueErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'InvalidParameterValue,*'
$invalidValueErrors [ 0 ] . CategoryInfo . Category | Should -Be 'InvalidArgument'
$invalidValueErrors [ 0 ] . Exception . Message | Should -Match ( [regex] :: Escape ( $Parameter ) )
if ( $ValueText ) {
$invalidValueErrors [ 0 ] . Exception . Message | Should -Match ( [regex] :: Escape ( $ValueText ) )
}
if ( $canReadAudit ) {
Get-TestAuditSddl -Path $path | Should -BeExactly $before
$pipelineErrors | Should -HaveCount 1
$result | Should -Not -BeNullOrEmpty
$result | ForEach-Object -Process { $_ . FullName | Should -Be $next }
}
}
# A descriptor that Get-NTFSSecurityDescriptor read without the Security privilege has no audit section, so
# without the privilege the "good" descriptor also gets its own (unrelated) ReadSecurityError; see above.
It 'Should write one InvalidParameterValue error per descriptor for <Cmdlet> -<Parameter> bound to a piped descriptor, and leave the descriptors unchanged' -ForEach $auditSdInvalidParameterCases {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'InvalidSdFirst' -Directory
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'InvalidSdSecond' -Directory
$descriptors = @ ( Get-NTFSSecurityDescriptor -Path $first , $second -ErrorAction Stop )
$before = $descriptors [ 0 ] . SecurityDescriptor . GetSecurityDescriptorSddlForm ( 'Access' )
$extra = @ { }
$extra [ $Parameter ] = $Value
$rows = @ (
[pscustomobject] ( @ { SecurityDescriptor = $descriptors [ 0 ] ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' } + $extra )
[pscustomobject] @ { SecurityDescriptor = $descriptors [ 1 ] ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' }
)
$result = @ ( $rows | & $Cmdlet -ErrorVariable pipelineErrors -ErrorAction SilentlyContinue )
$invalidValueErrors = @ ( $pipelineErrors | Where-Object -FilterScript { $_ . TargetObject -eq $descriptors [ 0 ] } )
$invalidValueErrors | Should -HaveCount 1
$invalidValueErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'InvalidParameterValue,*'
$invalidValueErrors [ 0 ] . CategoryInfo . Category | Should -Be 'InvalidArgument'
$descriptors [ 0 ] . SecurityDescriptor . GetSecurityDescriptorSddlForm ( 'Access' ) | Should -BeExactly $before
if ( $canReadAudit ) {
$pipelineErrors | Should -HaveCount 1
}
}
}
Context 'Continuation and no carry-over across piped objects' {
It 'Should apply both good objects when a bad AuditFlags value is between them, in the order of the rows' -Skip : ( -not $canReadAudit ) {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinueFirst' -Directory
$middle = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinueMiddle' -Directory
$last = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinueLast' -Directory
$rows = @ (
[pscustomobject] @ { FullName = $first ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; AuditFlags = 'Failure' }
[pscustomobject] @ { FullName = $middle ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; AuditFlags = 'Failurre' }
[pscustomobject] @ { FullName = $last ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; AuditFlags = 'Failure' }
)
$rows | Add-NTFSAudit -ErrorVariable pipelineErrors -ErrorAction SilentlyContinue
$pipelineErrors | Should -HaveCount 1
$pipelineErrors [ 0 ] . TargetObject | Should -Be $middle
$firstRule = Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $first -Audit ) -Account 'S-1-1-0'
$firstRule | Should -HaveCount 1
$firstRule [ 0 ] . AuditFlags | Should -Be ( [System.Security.AccessControl.AuditFlags] :: Failure )
$middleRule = Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $middle -Audit ) -Account 'S-1-1-0'
$middleRule | Should -BeNullOrEmpty
$lastRule = Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $last -Audit ) -Account 'S-1-1-0'
$lastRule | Should -HaveCount 1
$lastRule [ 0 ] . AuditFlags | Should -Be ( [System.Security.AccessControl.AuditFlags] :: Failure )
}
It 'Should give the row without AuditFlags the default Success and Failure after a preceding Failure-only row, with no carry-over' -Skip : ( -not $canReadAudit ) {
$failureFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'NoCarryOverFailure' -Directory
$defaultFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'NoCarryOverDefault' -Directory
@ (
[pscustomobject] @ { FullName = $failureFolder ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; AuditFlags = 'Failure' }
[pscustomobject] @ { FullName = $defaultFolder ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' }
) | Add-NTFSAudit -ErrorAction Stop
$failureRule = Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $failureFolder -Audit ) -Account 'S-1-1-0'
$failureRule | Should -HaveCount 1
$failureRule [ 0 ] . AuditFlags | Should -Be ( [System.Security.AccessControl.AuditFlags] :: Failure )
$defaultRule = Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $defaultFolder -Audit ) -Account 'S-1-1-0'
$defaultRule | Should -HaveCount 1
$defaultRule [ 0 ] . AuditFlags | Should -Be ( [System.Security.AccessControl.AuditFlags] 'Success, Failure' )
}
It 'Should give the row without <Parameter> its own default after a preceding row with an invalid value, with no carry-over' -ForEach $auditNoCarryOverCases -Skip : ( -not $canReadAudit ) {
$badFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'NoCarryOverBad' -Directory
$defaultFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'NoCarryOverAfterBad' -Directory
$extra = @ { }
$extra [ $Parameter ] = $Value
@ (
[pscustomobject] ( @ { FullName = $badFolder ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' } + $extra )
[pscustomobject] @ { FullName = $defaultFolder ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' }
) | Add-NTFSAudit -ErrorAction SilentlyContinue
$defaultRule = Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $defaultFolder -Audit ) -Account 'S-1-1-0'
$defaultRule | Should -HaveCount 1
$defaultRule [ 0 ] . AuditFlags | Should -Be ( [System.Security.AccessControl.AuditFlags] 'Success, Failure' )
$defaultRule [ 0 ] . InheritanceFlags | Should -Be ( [System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit' )
$defaultRule [ 0 ] . PropagationFlags | Should -Be ( [System.Security.AccessControl.PropagationFlags] :: None )
}
}
Context 'Valid values are unchanged for every shape' {
It 'Should add a Failure-only entry for AuditFlags given as <Description>' -ForEach $auditValidAuditFlagsShapeCases -Skip : ( -not $canReadAudit ) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ValidAuditFlags' -Directory
[pscustomobject] @ { FullName = $folder ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; AuditFlags = $Value } | Add-NTFSAudit -ErrorAction Stop
$rule = Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $folder -Audit ) -Account 'S-1-1-0'
$rule | Should -HaveCount 1
$rule [ 0 ] . AuditFlags . HasFlag ( [System.Security.AccessControl.AuditFlags] :: Failure ) | Should -BeTrue
}
It 'Should keep the default AuditFlags Success and Failure for a $null value' -Skip : ( -not $canReadAudit ) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ValidAuditFlagsNull' -Directory
[pscustomobject] @ { FullName = $folder ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; AuditFlags = $null } | Add-NTFSAudit -ErrorAction Stop
$rule = Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $folder -Audit ) -Account 'S-1-1-0'
$rule | Should -HaveCount 1
$rule [ 0 ] . AuditFlags | Should -Be ( [System.Security.AccessControl.AuditFlags] 'Success, Failure' )
}
It 'Should scope the entry with InheritanceFlags given as <Description>' -ForEach $auditValidFlagsShapeCases -Skip : ( -not $canReadAudit ) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ValidInheritanceFlags' -Directory
[pscustomobject] @ { FullName = $folder ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; InheritanceFlags = $Value ; PropagationFlags = 'None' } | Add-NTFSAudit -ErrorAction Stop
$rule = Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $folder -Audit ) -Account 'S-1-1-0'
$rule | Should -HaveCount 1
$rule [ 0 ] . InheritanceFlags | Should -Be ( [System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit' )
}
It 'Should keep the default flags for $null InheritanceFlags and PropagationFlags' -Skip : ( -not $canReadAudit ) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'ValidFlagsNull' -Directory
[pscustomobject] @ { FullName = $folder ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; InheritanceFlags = $null ; PropagationFlags = $null } | Add-NTFSAudit -ErrorAction Stop
$rule = Get-TestAccountAuditRule -Acl ( Get-Acl -LiteralPath $folder -Audit ) -Account 'S-1-1-0'
$rule | Should -HaveCount 1
$rule [ 0 ] . InheritanceFlags | Should -Be ( [System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit' )
$rule [ 0 ] . PropagationFlags | Should -Be ( [System.Security.AccessControl.PropagationFlags] :: None )
}
}
Context 'A bad value named on the command line' {
It 'Should throw a terminating InvalidParameterValue error for <Cmdlet> -<Parameter>, catchable with try/catch, and change nothing' -ForEach $auditNamedInvalidParameterCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedInvalid' -Directory
$before = if ( $canReadAudit ) { Get-TestAuditSddl -Path $folder }
$namedArguments = @ { Path = $folder ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' }
$namedArguments [ $Parameter ] = $Value
$caughtId = $null
try {
& $Cmdlet @namedArguments -ErrorAction Stop
}
catch {
$caughtId = $_ . FullyQualifiedErrorId
}
$caughtId | Should -BeLike 'InvalidParameterValue,*'
if ( $canReadAudit ) {
Get-TestAuditSddl -Path $folder | Should -BeExactly $before
}
}
It 'Should stop before the first piped path when -AuditFlags is named and bad' {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedBadFirst' -Directory
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'NamedBadSecond' -Directory
$before = if ( $canReadAudit ) { @ ( ( Get-TestAuditSddl -Path $first ) , ( Get-TestAuditSddl -Path $second ) ) }
$caughtId = $null
try {
$first , $second | Add-NTFSAudit -Account 'S-1-1-0' -AccessRights ReadData -AuditFlags Failurre -ErrorAction Stop
}
catch {
$caughtId = $_ . FullyQualifiedErrorId
}
$caughtId | Should -BeLike 'InvalidParameterValue,*'
if ( $canReadAudit ) {
Get-TestAuditSddl -Path $first | Should -BeExactly $before [ 0 ]
Get-TestAuditSddl -Path $second | Should -BeExactly $before [ 1 ]
}
}
}
Context 'Choosing the parameter set with a mix of string and enum values' {
It 'Should reject a piped object whose AppliesTo is <AppliesToShape> combined with InheritanceFlags as <FlagShape> with an AmbiguousParameterSet error' -ForEach $auditAmbiguousValueCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousPipedMix' -Directory
$appliesToValue = if ( $AppliesToIsEnum ) { [Security2.ApplyTo] :: ThisFolderOnly } else { 'ThisFolderOnly' }
$flagValue = if ( $FlagIsEnum ) { [System.Security.AccessControl.InheritanceFlags] :: None } else { 'None' }
$object = [pscustomobject] @ { FullName = $folder ; Account = 'S-1-1-0' ; AccessRights = 'ReadData' ; AppliesTo = $appliesToValue ; InheritanceFlags = $flagValue }
{ $object | Add-NTFSAudit -ErrorAction Stop } | Should -Throw -ErrorId 'AmbiguousParameterSet*'
}
It 'Should reject a named call whose -AppliesTo is <AppliesToShape> combined with -PropagationFlags as <FlagShape> with an AmbiguousParameterSet error' -ForEach $auditAmbiguousValueCases {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'AmbiguousNamedMix' -Directory
$appliesToValue = if ( $AppliesToIsEnum ) { [Security2.ApplyTo] :: ThisFolderOnly } else { 'ThisFolderOnly' }
$flagValue = if ( $FlagIsEnum ) { [System.Security.AccessControl.InheritanceFlags] :: None } else { 'None' }
{ Add-NTFSAudit -Path $folder -Account 'S-1-1-0' -AccessRights ReadData -AppliesTo $appliesToValue -PropagationFlags $flagValue -ErrorAction Stop } |
Should -Throw -ErrorId 'AmbiguousParameterSet*'
}
}
Context 'Tab completion and the parameter type' {
It '<Cmdlet> -<Parameter> should complete exactly the names of its enumeration' -ForEach $auditCompleterCases {
$inputScript = '{0} -{1} ' -f $Cmdlet , $Parameter
$enumType = [type] $EnumTypeName
$completion = [System.Management.Automation.CommandCompletion] :: CompleteInput ( $inputScript , $inputScript . Length , $null )
@ ( $completion . CompletionMatches | ForEach-Object -Process { $_ . CompletionText } ) | Should -BeExactly ( [Enum] :: GetNames ( $enumType ) )
}
It '<Cmdlet> should declare -<Parameter> as [object]' -ForEach $auditCompleterCases {
( Get-Command -Name $Cmdlet ) . Parameters [ $Parameter ] . ParameterType | Should -Be ( [object] )
}
}
}