Browse Source

fix: report a denied read of Get-NTFSOrphanedAccess as ReadSecurityError

Add tests for the error handling that the cmdlets with -Path share: a
path that doesn't exist (16 cmdlets, and 6 audit cmdlets with the
Security privilege), an item whose owner may not read its permissions
(6 cmdlets), and an item whose owner may not change them, which the 6
cmdlets that write the DACL handle by taking ownership. Each error
belongs to its path only, and the cmdlet goes on with the next one.

The tests found one defect: Get-NTFSOrphanedAccess reported an item that
it couldn't read as an AddAceError with the category WriteError. It now
writes a ReadSecurityError, like Get-NTFSAccess.

They also show that the take-ownership retry works without privileges
when the account holds the Take Ownership right and may assign the
previous owner, and that it can't help a denied read, because reading
the owner needs the same right. Concepts and five cmdlet pages said that
the retry needs the privileges; they now describe both, and that Windows
removes the OWNER RIGHTS entries when the owner changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
ai/release-5.0.0-rc6
Raimund Andree 4 days ago
parent
commit
db7387f172
  1. 3
      CHANGELOG.md
  2. 2
      Docs/Cmdlets/Add-NTFSAccess.md
  3. 2
      Docs/Cmdlets/Clear-NTFSAccess.md
  4. 2
      Docs/Cmdlets/Get-NTFSAccess.md
  5. 2
      Docs/Cmdlets/Get-NTFSOrphanedAccess.md
  6. 2
      Docs/Cmdlets/Remove-NTFSAccess.md
  7. 8
      Docs/Concepts.md
  8. 3
      NTFSSecurity/AccessCmdlets/GetOrphanedAccess.cs
  9. 10
      NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
  10. 232
      Tests/PathErrors.Tests.ps1

3
CHANGELOG.md

@ -304,5 +304,8 @@ The format is based on
for a descriptor that the cmdlet wrote as the owner, and which turned a
failed read after a successful write into another attempt of the write
and a `WriteSdError`; it now writes a `ReadSecurityError` for that read
- Fix `Get-NTFSOrphanedAccess`, which reported an item that it couldn't
read as an `AddAceError`; it now writes a `ReadSecurityError`, like
`Get-NTFSAccess`
[Unreleased]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...HEAD

2
Docs/Cmdlets/Add-NTFSAccess.md

@ -288,7 +288,7 @@ With `-PassThru`, the cmdlet writes all access control entries, explicit and inh
When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.
If the ACL of an item cannot be written because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Changing the owner of an item requires the Take Ownership and Restore privileges, so this fallback only succeeds in an elevated session of an account that holds them.
If the ACL of an item cannot be written because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. That fallback only succeeds when the account can take ownership of the item, through the Take Ownership right on the item or the Take Ownership privilege, and can set the previous owner back, which needs the Restore privilege unless that owner is the account itself or one of its groups. When the owner changes, Windows removes the entries for OWNER RIGHTS of the item.
In the `Path` parameter sets, the cmdlet reads and writes only the DACL of the item and leaves its owner, its group, and its SACL as they are. Before 5.0.0, it also wrote the owner back, which failed with error 1307, "This security ID may not be assigned as the owner of this object", when the account may not assign that owner, such as on some file servers. In an elevated session, it could also store the inherited entries of the item as explicit entries.

2
Docs/Cmdlets/Clear-NTFSAccess.md

@ -143,7 +143,7 @@ The cmdlet writes nothing. Use `Get-NTFSAccess` to inspect the result.
When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.
If the ACL of an item cannot be written because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Changing the owner of an item requires the Take Ownership and Restore privileges, so this fallback only succeeds in an elevated session of an account that holds them.
If the ACL of an item cannot be written because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. That fallback only succeeds when the account can take ownership of the item, through the Take Ownership right on the item or the Take Ownership privilege, and can set the previous owner back, which needs the Restore privilege unless that owner is the account itself or one of its groups. When the owner changes, Windows removes the entries for OWNER RIGHTS of the item.
In the `Path` parameter set, the cmdlet reads and writes only the DACL of the item and leaves its owner, its group, and its SACL as they are. Before 5.0.0, it also wrote the owner back, which failed with error 1307, "This security ID may not be assigned as the owner of this object", when the account may not assign that owner, such as on some file servers.

2
Docs/Cmdlets/Get-NTFSAccess.md

@ -180,7 +180,7 @@ One object per access control entry, with the account, the rights, the access ty
When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.
If the ACL of an item cannot be read because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Changing the owner of an item requires the Take Ownership and Restore privileges, so this fallback only succeeds in an elevated session of an account that holds them.
If the ACL of an item cannot be read because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Reading the owner, which that fallback needs first, requires the same Read Permissions right as reading the ACL, so the cmdlet then writes a non-terminating `ReadSecurityError` and continues with the next item.
Entries whose account cannot be translated into a name are returned with their SID. Use `Get-NTFSOrphanedAccess` to list only those entries.

2
Docs/Cmdlets/Get-NTFSOrphanedAccess.md

@ -172,7 +172,7 @@ One object per orphaned access control entry. The `Account` property holds the u
When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.
If the ACL of an item cannot be read because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Changing the owner of an item requires the Take Ownership and Restore privileges, so this fallback only succeeds in an elevated session of an account that holds them.
If the ACL of an item cannot be read because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Reading the owner, which that fallback needs first, requires the same Read Permissions right as reading the ACL, so the cmdlet then writes a non-terminating `ReadSecurityError` and continues with the next item. Before 5.0.0, it reported that error as an `AddAceError`.
Before 5.0.0, the cmdlet ignored `-Account` and `-SecurityDescriptor`, and after a path whose ACL could not be read, it returned the orphaned entries of the previous item again.

2
Docs/Cmdlets/Remove-NTFSAccess.md

@ -300,7 +300,7 @@ With `-PassThru`, the cmdlet writes all access control entries, explicit and inh
When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.
If the ACL of an item cannot be written because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Changing the owner of an item requires the Take Ownership and Restore privileges, so this fallback only succeeds in an elevated session of an account that holds them.
If the ACL of an item cannot be written because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. That fallback only succeeds when the account can take ownership of the item, through the Take Ownership right on the item or the Take Ownership privilege, and can set the previous owner back, which needs the Restore privilege unless that owner is the account itself or one of its groups. When the owner changes, Windows removes the entries for OWNER RIGHTS of the item.
Removing rights from an entry that does not exist is not an error; the cmdlet leaves the ACL unchanged.

8
Docs/Concepts.md

@ -209,7 +209,13 @@ state.
When reading or changing an item fails with an access-denied error, most of
these cmdlets make the current user the owner of the item, retry, and then
restore the previous owner, also when the retry fails. Before 5.0.0, a failed
retry left the current user as the owner. This requires the privileges above.
retry left the current user as the owner. Taking ownership needs the Take
Ownership right on the item or the Take Ownership privilege, and setting the
previous owner back needs the Restore privilege unless that owner is the user
or one of its groups. When the owner of an item changes, Windows removes its
entries for OWNER RIGHTS, so the retry removes such entries as well. For
reading, the retry doesn't help: the cmdlet must read the owner first, which
needs the same right as reading the permissions.
Reading or changing audit entries always requires the Security privilege.
Without it, the audit cmdlets fail, and `Get-NTFSEffectiveAccess` warns that

3
NTFSSecurity/AccessCmdlets/GetOrphanedAccess.cs

@ -55,7 +55,8 @@ namespace NTFSSecurity
}
catch (Exception ex2)
{
this.WriteError(new ErrorRecord(ex2, "AddAceError", ErrorCategory.WriteError, path));
// A read error; before 5.0.0-rc6, it was reported as an AddAceError.
this.WriteError(new ErrorRecord(ex2, "ReadSecurityError", ErrorCategory.ReadError, path));
continue;
}
}

10
NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml

@ -719,7 +719,7 @@
<maml:alertSet>
<maml:alert>
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>If the ACL of an item cannot be written because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Changing the owner of an item requires the Take Ownership and Restore privileges, so this fallback only succeeds in an elevated session of an account that holds them.</maml:para>
<maml:para>If the ACL of an item cannot be written because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. That fallback only succeeds when the account can take ownership of the item, through the Take Ownership right on the item or the Take Ownership privilege, and can set the previous owner back, which needs the Restore privilege unless that owner is the account itself or one of its groups. When the owner changes, Windows removes the entries for OWNER RIGHTS of the item.</maml:para>
<maml:para>In the `Path` parameter sets, the cmdlet reads and writes only the DACL of the item and leaves its owner, its group, and its SACL as they are. Before 5.0.0, it also wrote the owner back, which failed with error 1307, "This security ID may not be assigned as the owner of this object", when the account may not assign that owner, such as on some file servers. In an elevated session, it could also store the inherited entries of the item as explicit entries.</maml:para>
</maml:alert>
</maml:alertSet>
@ -1711,7 +1711,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<maml:alertSet>
<maml:alert>
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>If the ACL of an item cannot be written because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Changing the owner of an item requires the Take Ownership and Restore privileges, so this fallback only succeeds in an elevated session of an account that holds them.</maml:para>
<maml:para>If the ACL of an item cannot be written because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. That fallback only succeeds when the account can take ownership of the item, through the Take Ownership right on the item or the Take Ownership privilege, and can set the previous owner back, which needs the Restore privilege unless that owner is the account itself or one of its groups. When the owner changes, Windows removes the entries for OWNER RIGHTS of the item.</maml:para>
<maml:para>In the `Path` parameter set, the cmdlet reads and writes only the DACL of the item and leaves its owner, its group, and its SACL as they are. Before 5.0.0, it also wrote the owner back, which failed with error 1307, "This security ID may not be assigned as the owner of this object", when the account may not assign that owner, such as on some file servers.</maml:para>
</maml:alert>
</maml:alertSet>
@ -4585,7 +4585,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:alertSet>
<maml:alert>
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>If the ACL of an item cannot be read because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Changing the owner of an item requires the Take Ownership and Restore privileges, so this fallback only succeeds in an elevated session of an account that holds them.</maml:para>
<maml:para>If the ACL of an item cannot be read because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Reading the owner, which that fallback needs first, requires the same Read Permissions right as reading the ACL, so the cmdlet then writes a non-terminating `ReadSecurityError` and continues with the next item.</maml:para>
<maml:para>Entries whose account cannot be translated into a name are returned with their SID. Use `Get-NTFSOrphanedAccess` to list only those entries.</maml:para>
<maml:para>Before 5.0.0, after a path whose ACL could not be read, the cmdlet returned the entries of the previous item again.</maml:para>
<maml:para>For the root of a drive, such as `C:`, or of a volume, such as `\?\Volume{GUID}`, the cmdlets that read and change security use the root folder of the volume, like Explorer, `icacls`, and `Get-Acl`. Before 5.0.0, they read and changed the security descriptor of the drive itself, a device object with other entries.</maml:para>
@ -5741,7 +5741,7 @@ PS C:\&gt; Get-NTFSAudit -SecurityDescriptor $sd</dev:code>
<maml:alertSet>
<maml:alert>
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>If the ACL of an item cannot be read because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Changing the owner of an item requires the Take Ownership and Restore privileges, so this fallback only succeeds in an elevated session of an account that holds them.</maml:para>
<maml:para>If the ACL of an item cannot be read because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Reading the owner, which that fallback needs first, requires the same Read Permissions right as reading the ACL, so the cmdlet then writes a non-terminating `ReadSecurityError` and continues with the next item. Before 5.0.0, it reported that error as an `AddAceError`.</maml:para>
<maml:para>Before 5.0.0, the cmdlet ignored `-Account` and `-SecurityDescriptor`, and after a path whose ACL could not be read, it returned the orphaned entries of the previous item again.</maml:para>
</maml:alert>
</maml:alertSet>
@ -8425,7 +8425,7 @@ PS C:\Data&gt; Get-NTFSSecurityDescriptor</dev:code>
<maml:alertSet>
<maml:alert>
<maml:para>When the module setting `EnablePrivileges` is `$true` (the default in the `PrivateData` section of NTFSSecurity.psd1), this cmdlet tries to enable the Backup, Restore, Take Ownership, and Security privileges while it runs and disables the privileges it enabled when it finishes. These privileges are only available in an elevated session of an account that holds them, such as a member of the local Administrators group. If a privilege cannot be enabled, the cmdlet continues without it and writes a debug message.</maml:para>
<maml:para>If the ACL of an item cannot be written because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. Changing the owner of an item requires the Take Ownership and Restore privileges, so this fallback only succeeds in an elevated session of an account that holds them.</maml:para>
<maml:para>If the ACL of an item cannot be written because access is denied, the cmdlet tries once more after making the current account the owner of the item, and restores the previous owner afterwards. That fallback only succeeds when the account can take ownership of the item, through the Take Ownership right on the item or the Take Ownership privilege, and can set the previous owner back, which needs the Restore privilege unless that owner is the account itself or one of its groups. When the owner changes, Windows removes the entries for OWNER RIGHTS of the item.</maml:para>
<maml:para>Removing rights from an entry that does not exist is not an error; the cmdlet leaves the ACL unchanged.</maml:para>
<maml:para>In the `Path` parameter sets, the cmdlet writes only the DACL of the item and leaves its owner, its group, and its SACL as they are. Before 5.0.0, it could also write the owner back, which failed with error 1307, "This security ID may not be assigned as the owner of this object", when the account may not assign that owner, such as on some file servers.</maml:para>
<maml:para>An entry with a generic right, such as `GenericAll`, can be removed, for example by piping it from `Get-NTFSAccess`. Windows keeps generic rights in the inherit-only entries of folders. Before 5.0.0, the cmdlet failed for such an entry with the error "The value '269484032' is not valid for this usage of the type FileSystemRights".</maml:para>

232
Tests/PathErrors.Tests.ps1

@ -0,0 +1,232 @@
<#
Tests the error handling that the cmdlets with -Path share, with the module built in NTFSSecurity\bin\Release on
files in a sandbox folder: a path that doesn't exist, an item whose owner may not read its permissions, and an item
whose owner may not change its permissions, which the cmdlets that write the DACL handle by taking ownership. Each
error belongs to its path only, and the cmdlet continues with the next one. The tests turn the module setting
EnablePrivileges off, so that an elevated session meets the same denials as a basic user, and restore it.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
BeforeDiscovery {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
$holdsSecurityPrivilege = Test-PrivilegeHeld -Name 'SeSecurityPrivilege'
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
$readEntry = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData' }
# An audit entry on a file has no inheritance flags.
$auditEntry = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData'; InheritanceFlags = 'None'; PropagationFlags = 'None' }
# Output: whether the cmdlet writes an object for the next path, an existing file.
$missingPathCases = @(
@{ Command = 'Get-NTFSAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Add-NTFSAccess'; Parameters = $readEntry; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Remove-NTFSAccess'; Parameters = $readEntry; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Clear-NTFSAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Get-NTFSEffectiveAccess'; Parameters = @{ Account = 'S-1-1-0' }; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Get-NTFSOrphanedAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Get-NTFSInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Enable-NTFSAccessInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Disable-NTFSAccessInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Set-NTFSInheritance'; Parameters = @{ AccessInheritanceEnabled = $true }; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Get-NTFSOwner'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Set-NTFSOwner'; Parameters = @{ Account = $currentUser }; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Get-NTFSSecurityDescriptor'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Get-Item2'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true }
@{ Command = 'Get-FileHash2'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Get-NTFSHardLink'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true }
)
# The audit cmdlets need the Security privilege for the next path.
$missingPathAuditCases = @(
@{ Command = 'Get-NTFSAudit'; Parameters = @{} }
@{ Command = 'Add-NTFSAudit'; Parameters = $auditEntry }
@{ Command = 'Remove-NTFSAudit'; Parameters = $auditEntry }
@{ Command = 'Clear-NTFSAudit'; Parameters = @{} }
@{ Command = 'Enable-NTFSAuditInheritance'; Parameters = @{} }
@{ Command = 'Disable-NTFSAuditInheritance'; Parameters = @{} }
)
$deniedReadCases = @(
@{ Command = 'Get-NTFSAccess'; Parameters = @{}; Output = $true }
@{ Command = 'Get-NTFSEffectiveAccess'; Parameters = @{ Account = 'S-1-1-0' }; Output = $true }
@{ Command = 'Get-NTFSOrphanedAccess'; Parameters = @{}; Output = $false }
@{ Command = 'Get-NTFSInheritance'; Parameters = @{}; Output = $true }
@{ Command = 'Get-NTFSOwner'; Parameters = @{}; Output = $true }
@{ Command = 'Get-NTFSSecurityDescriptor'; Parameters = @{}; Output = $true }
)
}
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 'PathErrors'
Push-Location -LiteralPath $sandbox
$privateData = (Get-Module -Name NTFSSecurity).PrivateData
$enablePrivileges = $privateData['EnablePrivileges']
$privateData['EnablePrivileges'] = $false
$sidType = [System.Security.Principal.SecurityIdentifier]
function Get-TestMissingPath {
$path = Join-Path -Path $sandbox -ChildPath ('Missing-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8))
Assert-TestSandboxPath -Sandbox $sandbox -Path $path
$path
}
function Get-TestAcl {
# .NET, because Get-Acl of an elevated Windows PowerShell reads also items that deny reading their permissions.
# .NET Core has the method as an extension method.
param ([string] $Path)
$info = New-Object -TypeName 'System.IO.FileInfo' -ArgumentList $Path
if ($PSVersionTable.PSEdition -eq 'Desktop') {
$info.GetAccessControl()
}
else {
[System.IO.FileSystemAclExtensions]::GetAccessControl($info)
}
}
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'A path that does not exist' {
It '<Command> should write a <ErrorId> for it and continue with the next path' -ForEach $missingPathCases {
$missing = Get-TestMissingPath
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Next'
$output = @(& $Command -Path $missing, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)
$pathErrors | Should -HaveCount 1
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike "$ErrorId,*"
$pathErrors[0].TargetObject | Should -Be $missing
if ($Output) {
$output | Should -Not -BeNullOrEmpty
}
}
It '<Command> should write a ReadFileError for it and continue with the next path' -ForEach $missingPathAuditCases -Skip:(-not $holdsSecurityPrivilege) {
$privateData['EnablePrivileges'] = $true
try {
$missing = Get-TestMissingPath
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NextAudit'
& $Command -Path $missing, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue | Out-Null
}
finally {
$privateData['EnablePrivileges'] = $false
}
$pathErrors | Should -HaveCount 1
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
$pathErrors[0].TargetObject | Should -Be $missing
}
}
Describe 'An item whose owner may not read its permissions' {
# A deny entry for OWNER RIGHTS replaces the right of the owner to read the security descriptor. Taking ownership
# can't help: the cmdlet must read the owner first, which needs the same right. Before 5.0.0-rc6,
# Get-NTFSOrphanedAccess reported this as an AddAceError.
It '<Command> should write a ReadSecurityError for it and continue with the next path' -ForEach $deniedReadCases {
$blocked = New-TestSandboxItem -Sandbox $sandbox -Name 'Unreadable'
Block-TestReadPermission -Sandbox $sandbox -Path $blocked
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NextReadable'
$output = @(& $Command -Path $blocked, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)
$pathErrors | Should -HaveCount 1
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$pathErrors[0].TargetObject | Should -Be $blocked
if ($Output) {
$output | Should -Not -BeNullOrEmpty
}
}
}
Describe 'An item whose owner may not change its permissions' {
# A deny entry for OWNER RIGHTS replaces the right of the owner to change the DACL. The cmdlets take ownership,
# which Windows answers by removing the OWNER RIGHTS entries, write the DACL, and set the previous owner back.
BeforeEach {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Unchangeable'
$acl = Get-TestAcl -Path $file
$owner = $acl.GetOwner($sidType).Value
}
It 'Add-NTFSAccess should take ownership, add the entry, and set the owner back' {
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $owner
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -HaveCount 1
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-3-4' }) | Should -BeNullOrEmpty
}
It 'Remove-NTFSAccess should take ownership, remove the entry, and set the owner back' {
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Remove-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $owner
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -BeNullOrEmpty
}
It 'Clear-NTFSAccess should take ownership, remove the explicit entries, and set the owner back' {
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Clear-NTFSAccess -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $owner
@($acl.GetAccessRules($true, $false, $sidType)) | Should -BeNullOrEmpty
}
It 'Disable-NTFSAccessInheritance should take ownership, protect the DACL, and set the owner back' {
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Disable-NTFSAccessInheritance -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $owner
$acl.AreAccessRulesProtected | Should -BeTrue
}
It 'Enable-NTFSAccessInheritance should take ownership, let the DACL inherit, and set the owner back' {
Disable-NTFSAccessInheritance -Path $file
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Enable-NTFSAccessInheritance -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $owner
$acl.AreAccessRulesProtected | Should -BeFalse
}
It 'Set-NTFSInheritance should take ownership, protect the DACL, and set the owner back' {
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Set-NTFSInheritance -Path $file -AccessInheritanceEnabled $false -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $owner
$acl.AreAccessRulesProtected | Should -BeTrue
}
}
Loading…
Cancel
Save