You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1251 lines
81 KiB

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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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' {
# 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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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 @(
@{ Set = 'PathComplex'; ExtraProperties = @{ InheritanceFlags = 'None'; PropagationFlags = 'None' }; Named = @{} }
@{ 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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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
$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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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)
)
$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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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
# 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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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 &#39;budget&#39; 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&#39;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&#39;s type, account, rights, flags, and inherited state directly, independent of the Path set&#39;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&#39;s file. Co-authored-by: Copilot &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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
# A folder, so that the scope of the entry is observable; the piped AppliesTo property binds SDSimple, as it binds
# PathSimple for a path
$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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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
$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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
Get-TestAuditSddl -Path $goodFile | Should -BeExactly $auditBefore
Set-NTFSSecurityDescriptor -SecurityDescriptor $sdGood -ErrorAction Stop
(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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
Get-TestAuditSddl -Path $goodFile | Should -Not -BeExactly $auditBefore
Get-TestDaclSddl -Path $goodFile | Should -BeExactly $daclBefore
}
}
# 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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
Get-TestAuditScope -Acl (Get-Acl -LiteralPath $folder -Audit) | Should -BeExactly $Scope
}
It 'Should reject a piped object that has both AppliesTo and InheritanceFlags with an AmbiguousParameterSet error' {
$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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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'
}
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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
$folders = @('AddCsvAuditThisFolderOnly', 'AddCsvAuditSubfolders', 'AddCsvAuditInheritOnly' |
ForEach-Object -Process { New-TestSandboxItem -Sandbox $sandbox -Name $_ -Directory })
$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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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)
)
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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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&#39;s property could not be converted to the parameter&#39;s type: a misspelled AccessType such as &#34;Denyy&#34;, 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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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) {
$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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
ForEach-Object -Process { New-TestSandboxItem -Sandbox $sandbox -Name $_ -Directory })
$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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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)
)
fix(access): report a piped AccessType, AuditFlags, flags, or scope that isn&#39;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&#39;s property could not be converted to the parameter&#39;s type: a misspelled AccessType such as &#34;Denyy&#34;, 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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
2 weeks ago
Import-Csv -LiteralPath $csv | Add-NTFSAudit -ErrorVariable auditErrors -ErrorAction SilentlyContinue
fix(access): report a piped AccessType, AuditFlags, flags, or scope that isn&#39;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&#39;s property could not be converted to the parameter&#39;s type: a misspelled AccessType such as &#34;Denyy&#34;, 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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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]
$scopes = $folders | ForEach-Object -Process { Get-TestAuditScope -Acl (Get-Acl -LiteralPath $_ -Audit) }
fix(access): report a piped AccessType, AuditFlags, flags, or scope that isn&#39;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&#39;s property could not be converted to the parameter&#39;s type: a misspelled AccessType such as &#34;Denyy&#34;, 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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
2 weeks ago
$scopes -join '|' | Should -BeExactly 'None/None||ContainerInherit, ObjectInherit/InheritOnly'
}
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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
@($auditErrors) | Should -HaveCount 1
$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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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
}
}
}
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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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 @(
@{ Set = 'PathComplex'; ExtraProperties = @{ InheritanceFlags = 'None'; PropagationFlags = 'None' }; Named = @{} }
@{ 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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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)
)
$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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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
}
}
# 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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
Get-TestAuditScope -Acl (Get-Acl -LiteralPath $folder -Audit) | Should -BeExactly $Left
}
It 'Should reject a piped object that has both AppliesTo and InheritanceFlags with an AmbiguousParameterSet error' {
$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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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 &#39;budget&#39; 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&#39;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&#39;s type, account, rights, flags, and inherited state directly, independent of the Path set&#39;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&#39;s file. Co-authored-by: Copilot &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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 &#39;budget&#39; 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&#39;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&#39;s type, account, rights, flags, and inherited state directly, independent of the Path set&#39;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&#39;s file. Co-authored-by: Copilot &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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
}
}
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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 &#39;budget&#39; 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&#39;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&#39;s type, account, rights, flags, and inherited state directly, independent of the Path set&#39;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&#39;s file. Co-authored-by: Copilot &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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 &#39;budget&#39; 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&#39;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&#39;s type, account, rights, flags, and inherited state directly, independent of the Path set&#39;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&#39;s file. Co-authored-by: Copilot &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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 &#39;budget&#39; 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&#39;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&#39;s type, account, rights, flags, and inherited state directly, independent of the Path set&#39;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&#39;s file. Co-authored-by: Copilot &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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 &#39;budget&#39; 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&#39;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&#39;s type, account, rights, flags, and inherited state directly, independent of the Path set&#39;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&#39;s file. Co-authored-by: Copilot &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
3 weeks ago
}
fix(access): report a piped AccessType, AuditFlags, flags, or scope that isn&#39;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&#39;s property could not be converted to the parameter&#39;s type: a misspelled AccessType such as &#34;Denyy&#34;, 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&#39;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 &lt;223556219+Copilot@users.noreply.github.com&gt; Co-authored-by: AI Assistant &lt;ai@example.com&gt;
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])
}
}
}