Browse Source

test(access): close budget parameter-set gaps for the access cmdlets

Add 20 new It blocks (32 executed cases once the 13-scope -ForEach of
Add-NTFSAccess/SDSimple expands) to ParameterSets.Access.Tests.ps1, closing
every REMAINING item that worklist-access.md marks budget for rows 1-13:

- Add-NTFSAccess: several accounts and positional binding (PathComplex); a
  Deny entry's exact rights with no auto-added Synchronize (PathComplex and
  PathSimple); a denied write and a Deny entry without rights through
  PathSimple; all 13 -AppliesTo scopes of a security descriptor read back
  with .NET, piped and several (SDSimple); piped and several descriptors
  asserted for Add itself, not only as a fixture (SDComplex).
- Remove-NTFSAccess: the -PassThru output's count against the complete
  survivor list for several piped paths; several accounts and positional
  binding (PathComplex); a Deny entry's rights-reduction without
  -RemoveSpecific; a missing and a denied path, piped, several (PathSimple);
  rights-reduction without -RemoveSpecific and disk independence until
  Set-NTFSSecurityDescriptor, piped and several descriptors (SDSimple); the
  on-disk write-through only after Set-NTFSSecurityDescriptor, piped and
  several descriptors (SDComplex).
- Get-NTFSAccess: what the cmdlet returns without -Path, asserted on an
  actual emitted object's type (Path).
- Get-NTFSOrphanedAccess: the category of ReadSecurityError after a failed
  ownership retry (Path); -ExcludeInherited and an entry added to a
  descriptor only in memory (SD).
- Get-NTFSSimpleAccess: the category and target of a missing path's
  ReadError (Path).
- Get-NTFSEffectiveAccess: one call combining a failing and a succeeding
  path (Path).

Items the REASON marks MaintainerDecision, Limit, or would-pin are left
untouched, per the group notes of the brief. No product code changed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
pull/129/head
Raimund Andree 2 weeks ago
parent
commit
472003e49e
  1. 452
      Tests/ParameterSets.Access.Tests.ps1

452
Tests/ParameterSets.Access.Tests.ps1

@ -25,6 +25,24 @@ BeforeDiscovery {
@{ Name = 'Get-NTFSEffectiveAccess'; Types = @('Security2.FileSystemAccessRule2') }
@{ Name = 'Get-NTFSSimpleAccess'; Types = @('Security2.SimpleFileSystemAccessRule') }
)
# The 13 named -AppliesTo scopes, each as the ACE flags that PermissionScopes.Tests.ps1 also uses, so that an
# entry a security descriptor set added can be read back with .NET independently of the cmdlet.
$sdScopeCases = @(
@{ Name = 'ThisFolderOnly'; Inheritance = 'None'; Propagation = 'None' }
@{ Name = 'ThisFolderSubfoldersAndFiles'; Inheritance = 'ContainerInherit, ObjectInherit'; Propagation = 'None' }
@{ Name = 'ThisFolderAndSubfolders'; Inheritance = 'ContainerInherit'; Propagation = 'None' }
@{ Name = 'ThisFolderAndFiles'; Inheritance = 'ObjectInherit'; Propagation = 'None' }
@{ Name = 'SubfoldersAndFilesOnly'; Inheritance = 'ContainerInherit, ObjectInherit'; Propagation = 'InheritOnly' }
@{ Name = 'SubfoldersOnly'; Inheritance = 'ContainerInherit'; Propagation = 'InheritOnly' }
@{ Name = 'FilesOnly'; Inheritance = 'ObjectInherit'; Propagation = 'InheritOnly' }
@{ Name = 'ThisFolderSubfoldersAndFilesOneLevel'; Inheritance = 'ContainerInherit, ObjectInherit'; Propagation = 'NoPropagateInherit' }
@{ Name = 'ThisFolderAndSubfoldersOneLevel'; Inheritance = 'ContainerInherit'; Propagation = 'NoPropagateInherit' }
@{ Name = 'ThisFolderAndFilesOneLevel'; Inheritance = 'ObjectInherit'; Propagation = 'NoPropagateInherit' }
@{ Name = 'SubfoldersAndFilesOnlyOneLevel'; Inheritance = 'ContainerInherit, ObjectInherit'; Propagation = 'InheritOnly, NoPropagateInherit' }
@{ Name = 'SubfoldersOnlyOneLevel'; Inheritance = 'ContainerInherit'; Propagation = 'InheritOnly, NoPropagateInherit' }
@{ Name = 'FilesOnlyOneLevel'; Inheritance = 'ObjectInherit'; Propagation = 'InheritOnly, NoPropagateInherit' }
)
}
BeforeAll {
@ -265,6 +283,135 @@ Describe 'Add-NTFSAccess' {
$guestsRule[0].FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::ReadData) | Should -BeTrue
}
}
Context 'PathComplex parameter set, additional state assertions' {
It 'Should add an explicit allow entry for each of several accounts bound positionally, with the default scope' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'AddPositionalSeveral' -Directory
Add-NTFSAccess $folder @('S-1-1-0', 'S-1-5-32-546') 'ReadData' -ErrorAction Stop
foreach ($account in 'S-1-1-0', 'S-1-5-32-546') {
$rule = @((Get-Acl -LiteralPath $folder).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq $account })
$rule | Should -HaveCount 1
$rule[0].AccessControlType | Should -Be ([System.Security.AccessControl.AccessControlType]::Allow)
$rule[0].FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::ReadData) | Should -BeTrue
$rule[0].InheritanceFlags | Should -Be ([System.Security.AccessControl.InheritanceFlags] 'ContainerInherit, ObjectInherit')
$rule[0].PropagationFlags | Should -Be ([System.Security.AccessControl.PropagationFlags]::None)
}
}
# Only an Allow entry gets Synchronize added (the page); a Deny entry written through -Path must carry
# exactly the requested rights.
It 'Should write a Deny entry through -Path with exactly the requested rights and no Synchronize' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'AddDenySynchronize'
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData -AccessType Deny -ErrorAction Stop
$rule = @((Get-Acl -LiteralPath $file).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$rule | Should -HaveCount 1
$rule[0].AccessControlType | Should -Be ([System.Security.AccessControl.AccessControlType]::Deny)
$rule[0].FileSystemRights | Should -Be ([System.Security.AccessControl.FileSystemRights]::ReadData)
}
}
Context 'PathSimple parameter set, Deny entries and a denied write' {
BeforeAll {
# So that an elevated session meets the same denial as a basic user (see PathErrors.Tests.ps1).
$privateData['EnablePrivileges'] = $false
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
}
It 'Should report AddAceError by category and target for a denied write scoped by -AppliesTo, and leave the item unchanged' {
$blocked = New-TestSandboxItem -Sandbox $sandbox -Name 'AddSimpleBlocked'
Block-TestWritePermission -Sandbox $sandbox -Path $blocked
$before = (Get-Acl -LiteralPath $blocked).Sddl
Add-NTFSAccess -Path $blocked -Account 'S-1-1-0' -AccessRights ReadData -AppliesTo ThisFolderOnly -ErrorVariable addErrors -ErrorAction SilentlyContinue
$addErrors | Should -HaveCount 1
$addErrors[0].FullyQualifiedErrorId | Should -BeLike 'AddAceError,*'
$addErrors[0].CategoryInfo.Category | Should -Be 'WriteError'
$addErrors[0].TargetObject | Should -Be $blocked
(Get-Acl -LiteralPath $blocked).Sddl | Should -BeExactly $before
}
# .NET refuses to build a deny entry without rights for this parameter set too; the write-denial code path
# above is shared with PathComplex (Access.Tests.ps1:740), so only the categorization needs its own case here.
It 'Should write an AddAceError for a Deny entry without rights through the PathSimple set, and change nothing' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'AddSimpleDenyNone'
$before = (Get-Acl -LiteralPath $file).Sddl
$result = @(Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights None -AccessType Deny -AppliesTo ThisFolderOnly -PassThru -ErrorVariable addErrors -ErrorAction SilentlyContinue)
$result | Should -BeNullOrEmpty
$addErrors | Should -HaveCount 1
$addErrors[0].FullyQualifiedErrorId | Should -BeLike 'AddAceError,*'
$addErrors[0].CategoryInfo.Category | Should -Be 'WriteError'
(Get-Acl -LiteralPath $file).Sddl | Should -BeExactly $before
}
It 'Should write a Deny entry scoped by -AppliesTo with exactly the requested rights and no Synchronize' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'AddSimpleDenyScoped' -Directory
Add-NTFSAccess -Path $folder -Account 'S-1-1-0' -AccessRights ReadData -AccessType Deny -AppliesTo ThisFolderAndSubfolders -ErrorAction Stop
$rule = @((Get-Acl -LiteralPath $folder).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$rule | Should -HaveCount 1
$rule[0].AccessControlType | Should -Be ([System.Security.AccessControl.AccessControlType]::Deny)
$rule[0].FileSystemRights | Should -Be ([System.Security.AccessControl.FileSystemRights]::ReadData)
$rule[0].InheritanceFlags | Should -Be ([System.Security.AccessControl.InheritanceFlags]::ContainerInherit)
$rule[0].PropagationFlags | Should -Be ([System.Security.AccessControl.PropagationFlags]::None)
}
}
Context 'SDSimple parameter set' {
It 'Should add an entry with the scope <Name> to each of several descriptors bound through the pipeline, read back with .NET, and leave the items unchanged on disk' -ForEach $sdScopeCases {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'AddSdScopeFirst' -Directory
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'AddSdScopeSecond' -Directory
$before = foreach ($folder in $first, $second) { (Get-Acl -LiteralPath $folder).GetSecurityDescriptorSddlForm('Access') }
$descriptors = @(Get-NTFSSecurityDescriptor -Path $first, $second -ErrorAction Stop)
$descriptors | Add-NTFSAccess -Account 'S-1-5-32-546' -AccessRights ReadData -AppliesTo $Name -ErrorAction Stop
for ($index = 0; $index -lt 2; $index++) {
$rule = @($descriptors[$index].SecurityDescriptor.GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-32-546' })
$rule | Should -HaveCount 1
$rule[0].AccessControlType | Should -Be ([System.Security.AccessControl.AccessControlType]::Allow)
$rule[0].InheritanceFlags | Should -Be ([System.Security.AccessControl.InheritanceFlags] $Inheritance)
$rule[0].PropagationFlags | Should -Be ([System.Security.AccessControl.PropagationFlags] $Propagation)
}
(Get-Acl -LiteralPath $first).GetSecurityDescriptorSddlForm('Access') | Should -BeExactly $before[0]
(Get-Acl -LiteralPath $second).GetSecurityDescriptorSddlForm('Access') | Should -BeExactly $before[1]
}
}
Context 'SDComplex parameter set with several descriptors' {
It 'Should add the entry to each of several descriptors bound through the pipeline, read back with .NET, and leave the items unchanged on disk' {
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'AddSdComplexPipeFirst'
$second = New-TestSandboxItem -Sandbox $sandbox -Name 'AddSdComplexPipeSecond'
$before = foreach ($file in $first, $second) { (Get-Acl -LiteralPath $file).Sddl }
$descriptors = @(Get-NTFSSecurityDescriptor -Path $first, $second -ErrorAction Stop)
$descriptors | Add-NTFSAccess -Account 'S-1-5-21-1-2-3-9501' -AccessRights ReadData, WriteData -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
for ($index = 0; $index -lt 2; $index++) {
$rule = @($descriptors[$index].SecurityDescriptor.GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-21-1-2-3-9501' })
$rule | Should -HaveCount 1
$rule[0].FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::ReadData) | Should -BeTrue
$rule[0].FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::WriteData) | Should -BeTrue
}
(Get-Acl -LiteralPath $first).Sddl | Should -BeExactly $before[0]
(Get-Acl -LiteralPath $second).Sddl | Should -BeExactly $before[1]
}
}
}
Describe 'Remove-NTFSAccess' {
@ -387,6 +534,202 @@ Describe 'Remove-NTFSAccess' {
$guestsRule[0].FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::Delete) | Should -BeTrue
}
}
Context 'PathComplex parameter set, additional state assertions' {
It 'Should return exactly the surviving entries with -PassThru for each of several paths bound through the pipeline in one call' {
$keeper = 'S-1-1-0'
$target = 'S-1-5-32-546'
$folders = @(
New-TestSandboxItem -Sandbox $sandbox -Name 'RemovePassThruCountFirst' -Directory
New-TestSandboxItem -Sandbox $sandbox -Name 'RemovePassThruCountSecond' -Directory
)
foreach ($folder in $folders) {
Add-NTFSAccess -Path $folder -Account $keeper -AccessRights ReadData -ErrorAction Stop
Add-NTFSAccess -Path $folder -Account $target -AccessRights ReadData -ErrorAction Stop
}
# The independent oracle: every rule of the item that isn't the one being removed.
$expected = foreach ($folder in $folders) {
@((Get-Acl -LiteralPath $folder).GetAccessRules($true, $true, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -ne $target }).Count
}
$result = @(Get-Item2 -Path $folders | Remove-NTFSAccess -Account $target -AccessRights ReadData -PassThru -ErrorAction Stop)
for ($index = 0; $index -lt $folders.Count; $index++) {
$folderResult = @($result | Where-Object -FilterScript { $_.FullName -eq $folders[$index] })
$folderResult | Should -HaveCount $expected[$index]
$folderResult | ForEach-Object -Process { $_ | Should -BeOfType ([Security2.FileSystemAccessRule2]) }
@($folderResult | Where-Object -FilterScript { $_.Account.Sid -eq $target }) | Should -BeNullOrEmpty
@($folderResult | Where-Object -FilterScript { $_.Account.Sid -eq $keeper }) | Should -HaveCount 1
}
}
It 'Should remove the entries of several accounts bound positionally, leaving another account untouched' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'RemovePositionalSeveral' -Directory
Add-NTFSAccess -Path $folder -Account 'S-1-1-0' -AccessRights ReadData -ErrorAction Stop
Add-NTFSAccess -Path $folder -Account 'S-1-5-32-546' -AccessRights ReadData -ErrorAction Stop
Add-NTFSAccess -Path $folder -Account 'S-1-5-21-1-2-3-9701' -AccessRights ReadData -ErrorAction Stop
Remove-NTFSAccess $folder @('S-1-1-0', 'S-1-5-32-546') 'ReadData' -ErrorAction Stop
$rules = (Get-Acl -LiteralPath $folder).GetAccessRules($true, $false, $sidType)
@($rules | Where-Object -FilterScript { $_.IdentityReference.Value -in 'S-1-1-0', 'S-1-5-32-546' }) | Should -BeNullOrEmpty
@($rules | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-21-1-2-3-9701' }) | Should -HaveCount 1
}
}
Context 'PathSimple parameter set, a Deny entry and pipeline errors' {
BeforeAll {
# So that an elevated session meets the same denial as a basic user (see PathErrors.Tests.ps1). Harmless
# for the Deny-reduction test below, which never hits a permission denial.
$privateData['EnablePrivileges'] = $false
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
}
It 'Should take only the requested rights from a Deny entry scoped by -AppliesTo without -RemoveSpecific, and keep its flags' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSimpleDenyReduce' -Directory
Add-NTFSAccess -Path $folder -Account 'S-1-5-21-1-2-3-9702' -AccessRights Modify -AccessType Deny -AppliesTo ThisFolderAndSubfolders -ErrorAction Stop
Remove-NTFSAccess -Path $folder -Account 'S-1-5-21-1-2-3-9702' -AccessRights ReadData -AccessType Deny -AppliesTo ThisFolderAndSubfolders -ErrorAction Stop
$rule = @((Get-Acl -LiteralPath $folder).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-21-1-2-3-9702' })
$rule | Should -HaveCount 1
$rule[0].AccessControlType | Should -Be ([System.Security.AccessControl.AccessControlType]::Deny)
$rule[0].FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::ReadData) | Should -BeFalse
$rule[0].FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::WriteData) | Should -BeTrue
$rule[0].InheritanceFlags | Should -Be ([System.Security.AccessControl.InheritanceFlags]::ContainerInherit)
$rule[0].PropagationFlags | Should -Be ([System.Security.AccessControl.PropagationFlags]::None)
}
It 'Should report a missing path''s and a denied path''s errors by category and target, and still scope the surviving paths'' entries, all bound through the pipeline' {
$missing = Join-Path -Path $sandbox -ChildPath ('RemoveSimpleMissing-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8))
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing
$blocked = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSimpleBlocked' -Directory
$survivor1 = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSimpleSurvivor1' -Directory
$survivor2 = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSimpleSurvivor2' -Directory
foreach ($folder in $blocked, $survivor1, $survivor2) {
Add-NTFSAccess -Path $folder -Account 'S-1-1-0' -AccessRights ReadData -AppliesTo ThisFolderOnly -ErrorAction Stop
}
Block-TestWritePermission -Sandbox $sandbox -Path $blocked
$before = (Get-Acl -LiteralPath $blocked).Sddl
# Property-name binding of -Path, like the piped objects of the Add-NTFSAccess pipeline tests; -Account,
# -AccessRights and -AppliesTo come from the call itself and apply to every piped object alike.
$paths = foreach ($value in $missing, $blocked, $survivor1, $survivor2) { [pscustomobject]@{ Path = $value } }
$result = @($paths | Remove-NTFSAccess -Account 'S-1-1-0' -AccessRights ReadData -AppliesTo ThisFolderOnly -PassThru -ErrorVariable removeErrors -ErrorAction SilentlyContinue)
$removeErrors | Should -HaveCount 2
$missingError = @($removeErrors | Where-Object -FilterScript { $_.TargetObject -eq $missing })
$missingError | Should -HaveCount 1
$missingError[0].FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
$missingError[0].CategoryInfo.Category | Should -Be 'OpenError'
$blockedError = @($removeErrors | Where-Object -FilterScript { $_.TargetObject -eq $blocked })
$blockedError | Should -HaveCount 1
$blockedError[0].FullyQualifiedErrorId | Should -BeLike 'RemoveAceError,*'
$blockedError[0].CategoryInfo.Category | Should -Be 'WriteError'
(Get-Acl -LiteralPath $blocked).Sddl | Should -BeExactly $before
foreach ($survivor in $survivor1, $survivor2) {
@((Get-Acl -LiteralPath $survivor).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -BeNullOrEmpty
@($result | Where-Object -FilterScript { $_.FullName -eq $survivor }) | Should -Not -BeNullOrEmpty
}
}
}
Context 'SDSimple parameter set' {
It 'Should take only the requested rights from an entry scoped by -AppliesTo on a descriptor without -RemoveSpecific, keep its flags, and leave the item on disk unchanged until Set-NTFSSecurityDescriptor' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSdSimpleReduce' -Directory
Add-NTFSAccess -Path $folder -Account 'S-1-5-21-1-2-3-9602' -AccessRights Modify -AppliesTo ThisFolderAndSubfolders -ErrorAction Stop
$before = (Get-Acl -LiteralPath $folder).Sddl
$sd = Get-NTFSSecurityDescriptor -Path $folder -ErrorAction Stop
Remove-NTFSAccess -SecurityDescriptor $sd -Account 'S-1-5-21-1-2-3-9602' -AccessRights ReadData -AppliesTo ThisFolderAndSubfolders -ErrorAction Stop
$rule = @($sd.SecurityDescriptor.GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-21-1-2-3-9602' })
$rule | Should -HaveCount 1
$rule[0].FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::ReadData) | Should -BeFalse
$rule[0].FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::WriteData) | Should -BeTrue
$rule[0].InheritanceFlags | Should -Be ([System.Security.AccessControl.InheritanceFlags]::ContainerInherit)
$rule[0].PropagationFlags | Should -Be ([System.Security.AccessControl.PropagationFlags]::None)
(Get-Acl -LiteralPath $folder).Sddl | Should -BeExactly $before
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop
$onDisk = @((Get-Acl -LiteralPath $folder).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-21-1-2-3-9602' })
$onDisk | Should -HaveCount 1
$onDisk[0].FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::ReadData) | Should -BeFalse
}
It 'Should remove the matching entry from each of several descriptors bound through the pipeline for the Simple parameter set, leaving the items unchanged on disk' {
$folders = @(
New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSdSimplePipeFirst' -Directory
New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSdSimplePipeSecond' -Directory
)
foreach ($folder in $folders) {
Add-NTFSAccess -Path $folder -Account 'S-1-5-21-1-2-3-9603' -AccessRights ReadData -AppliesTo ThisFolderOnly -ErrorAction Stop
}
$descriptors = @(Get-NTFSSecurityDescriptor -Path $folders -ErrorAction Stop)
$descriptors | Remove-NTFSAccess -Account 'S-1-5-21-1-2-3-9603' -AccessRights ReadData -AppliesTo ThisFolderOnly -RemoveSpecific -ErrorAction Stop
foreach ($sd in $descriptors) {
@($sd.SecurityDescriptor.GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-21-1-2-3-9603' }) | Should -BeNullOrEmpty
}
foreach ($folder in $folders) {
@((Get-Acl -LiteralPath $folder).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-21-1-2-3-9603' }) | Should -HaveCount 1
}
}
}
Context 'SDComplex parameter set' {
It 'Should remove an on-disk entry from a descriptor in memory only, and write the change through only when Set-NTFSSecurityDescriptor runs' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSdWriteThrough'
Add-NTFSAccess -Path $file -Account 'S-1-5-21-1-2-3-9601' -AccessRights ReadData -ErrorAction Stop
$sd = Get-NTFSSecurityDescriptor -Path $file -ErrorAction Stop
Remove-NTFSAccess -SecurityDescriptor $sd -Account 'S-1-5-21-1-2-3-9601' -AccessRights ReadData -ErrorAction Stop
@($sd.SecurityDescriptor.GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-21-1-2-3-9601' }) | Should -BeNullOrEmpty
@((Get-Acl -LiteralPath $file).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-21-1-2-3-9601' }) | Should -HaveCount 1
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop
@((Get-Acl -LiteralPath $file).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-21-1-2-3-9601' }) | Should -BeNullOrEmpty
}
It 'Should remove the matching entry from each of several descriptors bound through the pipeline for the Complex parameter set, leaving the items unchanged on disk' {
$files = @(
New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSdComplexPipeFirst'
New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSdComplexPipeSecond'
)
foreach ($file in $files) {
Add-NTFSAccess -Path $file -Account 'S-1-5-21-1-2-3-9604' -AccessRights ReadData -ErrorAction Stop
}
$descriptors = @(Get-NTFSSecurityDescriptor -Path $files -ErrorAction Stop)
$descriptors | Remove-NTFSAccess -Account 'S-1-5-21-1-2-3-9604' -AccessRights ReadData -ErrorAction Stop
foreach ($sd in $descriptors) {
@($sd.SecurityDescriptor.GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-21-1-2-3-9604' }) | Should -BeNullOrEmpty
}
foreach ($file in $files) {
@((Get-Acl -LiteralPath $file).GetAccessRules($true, $false, $sidType) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-5-21-1-2-3-9604' }) | Should -HaveCount 1
}
}
}
}
Describe 'Clear-NTFSAccess' {
@ -518,6 +861,29 @@ Describe 'Get-NTFSAccess' {
}
}
Context 'Path parameter set without -Path' {
It 'Should return the entries of the current location as actual Security2.FileSystemAccessRule2 objects when -Path is omitted' {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'GetAccessNoPath' -Directory
Add-NTFSAccess -Path $folder -Account 'S-1-1-0' -AccessRights ReadData -ErrorAction Stop
$expected = @(Get-NTFSAccess -Path $folder -ErrorAction Stop)
Push-Location -LiteralPath $folder
try {
$result = @(Get-NTFSAccess -ErrorAction Stop)
}
finally {
Pop-Location
}
$result | Should -HaveCount $expected.Count
$result | ForEach-Object -Process {
$_ | Should -BeOfType ([Security2.FileSystemAccessRule2])
$_.FullName | Should -Be $folder
}
@($result | Where-Object -FilterScript { $_.Account.Sid -eq 'S-1-1-0' -and -not $_.IsInherited }) | Should -HaveCount 1
}
}
Context 'SD parameter set' {
It 'Should filter an entry added to a descriptor in memory by -Account, partition it with -ExcludeInherited and -ExcludeExplicit, and read two descriptors bound through the pipeline' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'GetAccessSdFilter'
@ -595,6 +961,36 @@ Describe 'Get-NTFSOrphanedAccess' {
}
}
Context 'Path parameter set, unreadable folder' {
BeforeAll {
# Forcing EnablePrivileges off reproduces the same denial as a basic user for an elevated session, as
# PathErrors.Tests.ps1 does for the same scenario.
$privateData['EnablePrivileges'] = $false
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
}
# Unlike Get-NTFSAccess, ReadSecurityError of this cmdlet has one consistent category after a failed
# ownership retry: ReadError (GetOrphanedAccess.cs), so it is safe to pin.
It 'Should write a ReadSecurityError categorized as ReadError after a failed ownership retry, and still return the next path''s orphaned entry' {
$blocked = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedUnreadable'
Block-TestReadPermission -Sandbox $sandbox -Path $blocked
$next = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedUnreadableNext'
Add-NTFSAccess -Path $next -Account 'S-1-5-21-1-2-3-9301' -AccessRights ReadData -ErrorAction Stop
$result = @(Get-NTFSOrphanedAccess -Path $blocked, $next -ErrorVariable orphanErrors -ErrorAction SilentlyContinue)
$orphanErrors | Should -HaveCount 1
$orphanErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$orphanErrors[0].CategoryInfo.Category | Should -Be 'ReadError'
$orphanErrors[0].TargetObject | Should -Be $blocked
$result | Should -HaveCount 1
"$($result[0].Account.Sid)" | Should -Be 'S-1-5-21-1-2-3-9301'
}
}
Context 'SD parameter set' {
It 'Should filter a descriptor''s orphaned entries by -Account, report both orphaned Sids by default, exclude them all with -ExcludeExplicit, and read two descriptors bound through the pipeline' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedSdFile'
@ -620,6 +1016,28 @@ Describe 'Get-NTFSOrphanedAccess' {
@($piped | Where-Object -FilterScript { $_.FullName -eq $file2 }) | Should -HaveCount 1
}
}
Context 'SD parameter set, -ExcludeInherited and an in-memory change' {
It 'Should return the explicit entry with -ExcludeInherited and still report an entry added to the descriptor only in memory' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedSdMemory'
Add-NTFSAccess -Path $file -Account 'S-1-5-21-1-2-3-9401' -AccessRights ReadData -ErrorAction Stop
$sd = Get-NTFSSecurityDescriptor -Path $file -ErrorAction Stop
$explicitOnly = @(Get-NTFSOrphanedAccess -SecurityDescriptor $sd -ExcludeInherited -ErrorAction Stop)
$explicitOnly | Should -HaveCount 1
"$($explicitOnly[0].Account.Sid)" | Should -Be 'S-1-5-21-1-2-3-9401'
Add-NTFSAccess -SecurityDescriptor $sd -Account 'S-1-5-21-1-2-3-9402' -AccessRights ReadData -ErrorAction Stop
$afterMemoryChange = @(Get-NTFSOrphanedAccess -SecurityDescriptor $sd -ErrorAction Stop)
($afterMemoryChange | ForEach-Object -Process { "$($_.Account.Sid)" } | Sort-Object) -join ',' |
Should -Be (('S-1-5-21-1-2-3-9401', 'S-1-5-21-1-2-3-9402' | Sort-Object) -join ',')
# The disk copy stays as it was: only the descriptor in memory saw the second account.
@(Get-NTFSOrphanedAccess -Path $file -ErrorAction Stop) | Should -HaveCount 1
}
}
}
Describe 'Get-NTFSSimpleAccess' {
@ -653,6 +1071,25 @@ Describe 'Get-NTFSSimpleAccess' {
}
}
Context 'Path parameter set, missing path' {
# Unlike the unreadable-folder case above, a missing path needs no retry and no privilege change: this cmdlet
# does not retry as the owner, so the ReadError of a missing path was pinned by ErrorId only until now.
It 'Should report a missing path''s error by category and target and still report the next folder''s entries' {
$missing = Join-Path -Path $sandbox -ChildPath ('SimpleMissing-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8))
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing
$next = New-TestSandboxItem -Sandbox $sandbox -Name 'SimpleMissingNext' -Directory
Add-NTFSAccess -Path $next -Account 'S-1-1-0' -AccessRights ReadData -ErrorAction Stop
$result = @(Get-NTFSSimpleAccess -Path $missing, $next -IncludeRootFolder:$false -ErrorVariable simpleErrors -ErrorAction SilentlyContinue)
$simpleErrors | Should -HaveCount 1
$simpleErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadError,*'
$simpleErrors[0].CategoryInfo.Category | Should -Be 'OpenError'
$simpleErrors[0].TargetObject | Should -Be $missing
@($result | Where-Object -FilterScript { $_.FullName -eq $next }) | Should -Not -BeNullOrEmpty
}
}
Context 'SD parameter set' {
It 'Should filter and partition a descriptor''s entries like the equivalent -Path call, and read two descriptors bound through the pipeline' {
$parent = New-TestSandboxItem -Sandbox $sandbox -Name 'SimpleSdParent' -Directory
@ -708,6 +1145,21 @@ Describe 'Get-NTFSEffectiveAccess' {
$result[0].FullName | Should -Be $first
$result[1].FullName | Should -Be $second
}
It 'Should write nothing for a missing path and only the succeeding path''s result in the same call' {
$missing = Join-Path -Path $sandbox -ChildPath ('EffectiveMissing-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8))
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing
$next = New-TestSandboxItem -Sandbox $sandbox -Name 'EffectiveMissingNext'
$result = @(Get-NTFSEffectiveAccess -Path $missing, $next -Account 'S-1-1-0' -WarningAction SilentlyContinue -ErrorVariable accessErrors -ErrorAction SilentlyContinue)
$accessErrors | Should -HaveCount 1
$accessErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
$accessErrors[0].CategoryInfo.Category | Should -Be 'OpenError'
$accessErrors[0].TargetObject | Should -Be $missing
$result | Should -HaveCount 1
$result[0].FullName | Should -Be $next
}
}
Context 'SecurityDescriptor parameter set' {

Loading…
Cancel
Save