diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96e2d60..41e112b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -234,10 +234,11 @@ The format is based on
- Fix `Clear-NTFSAudit`, which finished without an error but changed nothing
in a session without the Security privilege; it now writes an error, like
the other audit cmdlets
-- Fix the cmdlets for the root of a drive, such as `C:\`, which read and
- changed the security descriptor of the drive, a device object, instead of
- that of its root folder, so that `Get-NTFSAccess` showed other entries than
- Explorer ([#41](https://github.com/raandree/NTFSSecurity/issues/41))
+- Fix the cmdlets for the root of a drive, such as `C:\`, or of a volume,
+ such as `\\?\Volume{GUID}\`, which read and changed the security
+ descriptor of the drive, a device object, instead of that of its root
+ folder, so that `Get-NTFSAccess` showed other entries than Explorer
+ ([#41](https://github.com/raandree/NTFSSecurity/issues/41))
- Fix `Add-NTFSAudit`, `Remove-NTFSAudit`, and `Clear-NTFSAudit` with a
security descriptor that was read without the audit entries; they now
write an error like `Get-NTFSAudit` instead of changing the missing entries
diff --git a/Docs/Cmdlets/Get-NTFSAccess.md b/Docs/Cmdlets/Get-NTFSAccess.md
index e8a3392..5e407db 100644
--- a/Docs/Cmdlets/Get-NTFSAccess.md
+++ b/Docs/Cmdlets/Get-NTFSAccess.md
@@ -186,7 +186,7 @@ Entries whose account cannot be translated into a name are returned with their S
Before 5.0.0, after a path whose ACL could not be read, the cmdlet returned the entries of the previous item again.
-For the root of a drive, such as `C:\`, the cmdlets of the module read and change 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.
+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.
## RELATED LINKS
diff --git a/NTFSSecurity/BaseCmdlets.cs b/NTFSSecurity/BaseCmdlets.cs
index 2e53149..91b5353 100644
--- a/NTFSSecurity/BaseCmdlets.cs
+++ b/NTFSSecurity/BaseCmdlets.cs
@@ -33,7 +33,7 @@ namespace NTFSSecurity
}
var ex = new InvalidOperationException(string.Format(
- "The security descriptor of '{0}' doesn't contain the audit entries, because it was read without the Security privilege.", sd.FullName));
+ "The security descriptor of '{0}' doesn't contain the audit entries. Read it with Get-NTFSSecurityDescriptor in a session that holds the Security privilege.", sd.FullName));
WriteError(new ErrorRecord(ex, "ReadSecurityError", ErrorCategory.InvalidData, sd));
return false;
}
diff --git a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
index 49b6417..587951c 100644
--- a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
+++ b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
@@ -4588,7 +4588,7 @@ PS C:\> Disable-Privileges
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.
Entries whose account cannot be translated into a name are returned with their SID. Use `Get-NTFSOrphanedAccess` to list only those entries.
Before 5.0.0, after a path whose ACL could not be read, the cmdlet returned the entries of the previous item again.
- For the root of a drive, such as `C:`, the cmdlets of the module read and change 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.
+ 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.
diff --git a/Security2/FileSystem/FileSystemSecurity2.cs b/Security2/FileSystem/FileSystemSecurity2.cs
index a5673b1..76c590d 100644
--- a/Security2/FileSystem/FileSystemSecurity2.cs
+++ b/Security2/FileSystem/FileSystemSecurity2.cs
@@ -119,7 +119,16 @@ namespace Security2
private static bool TryGetDriveRoot(FileSystemInfo item, out string root)
{
var fullName = item.FullName.TrimEnd('\\');
- if (fullName.Length == 2 && fullName[1] == ':' && char.IsLetter(fullName[0]))
+
+ // A drive letter, such as C:
+ var isDriveLetter = fullName.Length == 2 && fullName[1] == ':' &&
+ ((fullName[0] >= 'A' && fullName[0] <= 'Z') || (fullName[0] >= 'a' && fullName[0] <= 'z'));
+
+ // A volume name, such as \\?\Volume{9f122b1b-858a-49bd-aec4-dfbe8978fe16}, without a folder below it
+ var isVolumeName = fullName.StartsWith(@"\\?\Volume{", StringComparison.OrdinalIgnoreCase) &&
+ fullName.EndsWith("}", StringComparison.Ordinal) && fullName.IndexOf('\\', 4) < 0;
+
+ if (isDriveLetter || isVolumeName)
{
root = fullName + "\\";
return true;
diff --git a/Tests/Access.Tests.ps1 b/Tests/Access.Tests.ps1
index 763a143..faa4538 100644
--- a/Tests/Access.Tests.ps1
+++ b/Tests/Access.Tests.ps1
@@ -128,7 +128,7 @@ Describe 'Get-NTFSEffectiveAccess' {
$accessErrors | Should -HaveCount 1
$accessErrors[0].FullyQualifiedErrorId | Should -BeLike 'GetEffectiveAccessError,*'
- $accessErrors[0].Exception.Message | Should -Not -BeLike '*privilege*'
+ $accessErrors[0].Exception.Message | Should -Not -BeLike '*Enable-Privileges*'
}
}
}
@@ -443,7 +443,16 @@ Describe 'Security descriptor parameter sets' {
# Before 5.0.0, PowerShell could not choose between the SDSimple and SDComplex parameter sets.
It '<_> should accept -SecurityDescriptor without -AppliesTo or the flag parameters' -ForEach @(
- 'Add-NTFSAccess', 'Remove-NTFSAccess', 'Add-NTFSAudit', 'Remove-NTFSAudit'
+ 'Add-NTFSAccess', 'Remove-NTFSAccess'
+ ) {
+ $sd = Get-NTFSSecurityDescriptor -Path $folder
+
+ { & $_ -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData -ErrorAction Stop } | Should -Not -Throw
+ }
+
+ # A descriptor from Get-NTFSSecurityDescriptor contains the audit entries only with the Security privilege.
+ It '<_> should accept -SecurityDescriptor without -AppliesTo or the flag parameters' -Skip:(-not $holdsSecurityPrivilege) -ForEach @(
+ 'Add-NTFSAudit', 'Remove-NTFSAudit'
) {
$sd = Get-NTFSSecurityDescriptor -Path $folder
diff --git a/Tests/Audit.Tests.ps1 b/Tests/Audit.Tests.ps1
index 2ae8816..1c58430 100644
--- a/Tests/Audit.Tests.ps1
+++ b/Tests/Audit.Tests.ps1
@@ -99,7 +99,8 @@ Describe 'Add-NTFSAudit' {
$positions['AccessRights'] | Should -Be 3
}
- It 'Should bind an account and access rights that are passed by position' {
+ # A descriptor from Get-NTFSSecurityDescriptor contains the audit entries only with the Security privilege.
+ It 'Should bind an account and access rights that are passed by position' -Skip:(-not $canReadAudit) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Positional'
$sd = Get-NTFSSecurityDescriptor -Path $file
@@ -110,7 +111,8 @@ Describe 'Add-NTFSAudit' {
}
}
- Context 'With -PassThru' {
+ # A descriptor from Get-NTFSSecurityDescriptor contains the audit entries only with the Security privilege.
+ Context 'With -PassThru' -Skip:(-not $canReadAudit) {
It 'Should return the audit entries of a security descriptor, not its access entries' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'PassThru'
$sd = Get-NTFSSecurityDescriptor -Path $file
@@ -222,7 +224,8 @@ Describe 'Remove-NTFSAudit' {
}
}
- Context 'With -RemoveSpecific' {
+ # A descriptor from Get-NTFSSecurityDescriptor contains the audit entries only with the Security privilege.
+ Context 'With -RemoveSpecific' -Skip:(-not $canReadAudit) {
BeforeEach {
$removeFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSpecific' -Directory
$sd = Get-NTFSSecurityDescriptor -Path $removeFolder
@@ -299,6 +302,8 @@ Describe 'Audit cmdlets with a security descriptor without the audit entries' {
$result | Should -BeNullOrEmpty
$auditErrors | Should -HaveCount 1
$auditErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
+ # The descriptor was read with the access entries only, not without the Security privilege.
+ $auditErrors[0].Exception.Message | Should -Not -BeLike '*because it was read without the Security privilege*'
$sd.SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit') | Should -BeNullOrEmpty
}
}
diff --git a/Tests/DriveRoot.Tests.ps1 b/Tests/DriveRoot.Tests.ps1
index e39d751..ba5d199 100644
--- a/Tests/DriveRoot.Tests.ps1
+++ b/Tests/DriveRoot.Tests.ps1
@@ -39,4 +39,15 @@ Describe 'The root folder of a drive' {
It 'Get-NTFSOwner should return the owner of the root folder' {
(Get-NTFSOwner -Path $root).Owner.Sid | Should -Be $acl.GetOwner($sidType).Value
}
+
+ It 'Get-NTFSAccess should return the access entries of the root folder for the volume name, such as \\?\Volume{GUID}\' {
+ # Win32_Volume returns nothing to a user without elevation; mountvol works for every user.
+ $volume = (mountvol.exe $root /L | Out-String).Trim()
+ $volume | Should -BeLike '\\?\Volume{*}\'
+ $expected = @($acl.GetAccessRules($true, $true, $sidType) | ForEach-Object -Process { $_.IdentityReference.Value } | Sort-Object)
+
+ $entries = @(Get-NTFSAccess -Path $volume)
+
+ @($entries | ForEach-Object -Process { $_.Account.Sid } | Sort-Object) | Should -Be $expected
+ }
}