Browse Source

test: close the follow-ups of #110 that tests can reach

- Remove-NTFSAccess and Remove-NTFSAudit with -RemoveSpecific are tested
  also with -Path, not only with -SecurityDescriptor.
- Copy-Item2 and Move-Item2 name the destination of a folder in their
  verbose message, not only of a file.
- The Enable-Privileges count compares with the privileges of the token,
  so that it passes as a basic user, whose token holds one; the tests of
  -PassThru restore the privilege states that they found.
- The type name comparison of Get-FileHash2 is exact, the inherited-entry
  counts must be greater than zero, and the braces test (#3) checks the
  verbose message that raised the FormatException.
- Remove-TestSandbox removes paths longer than 260 characters in Windows
  PowerShell, through the \\?\ prefix and rd, so the long-path test of
  Test-Path2 no longer cleans up itself; after a failed setup, it returns
  instead of stopping AfterAll with a binding error.

Not reachable by a test: a second path whose SACL read fails while the
Security privilege is enabled; a declined -Confirm takes the code path of
-WhatIf. The tests that need a session without privileges get the CI run
as a basic user.

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
bbac9ac289
  1. 26
      Tests/Access.Tests.ps1
  2. 21
      Tests/Audit.Tests.ps1
  3. 2
      Tests/Inheritance.Tests.ps1
  4. 23
      Tests/ItemCmdlets.Tests.ps1
  5. 22
      Tests/OutputTypes.Tests.ps1
  6. 1
      Tests/SecurityDescriptorSets.Tests.ps1
  7. 22
      Tests/TestHelpers.Tests.ps1
  8. 37
      Tests/TestHelpers.psm1

26
Tests/Access.Tests.ps1

@ -152,13 +152,15 @@ Describe 'Get-NTFSEffectiveAccess' {
}
Describe 'Get-NTFSOrphanedAccess' {
# Before 5.0.0, a path with braces stopped the cmdlet with a FormatException (#3).
# Before 5.0.0, a path with braces stopped the cmdlet with a FormatException (#3), which the verbose message raised.
It 'Should read a folder whose name contains braces' {
$braces = Join-Path -Path $sandbox -ChildPath ('{{Braces}}-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8))
Assert-TestSandboxPath -Sandbox $sandbox -Path $braces
[IO.Directory]::CreateDirectory($braces) | Out-Null
{ Get-NTFSOrphanedAccess -Path $braces -ErrorAction Stop } | Should -Not -Throw
$messages = @(Get-NTFSOrphanedAccess -Path $braces -Verbose -ErrorAction Stop 4>&1)
$messages.Message | Should -Contain "Item $braces knows about 0 orphaned SIDs in its ACL"
}
BeforeAll {
@ -549,6 +551,26 @@ Describe 'Remove-NTFSAccess' {
$rule | Should -Not -BeNullOrEmpty
$rule.FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::ReadData) | Should -BeFalse
}
It 'Should keep an entry that does not match exactly, given the path' {
Add-NTFSAccess -Path $removeFolder -Account 'Everyone' -AccessRights Modify
Remove-NTFSAccess -Path $removeFolder -Account 'Everyone' -AccessRights ReadData -RemoveSpecific -ErrorAction Stop
$rules = @((Get-Acl -LiteralPath $removeFolder).GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' })
$rules | Should -HaveCount 1
$rules[0].FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::Modify) | Should -BeTrue
}
It 'Should remove an entry that matches exactly, given the path' {
Add-NTFSAccess -Path $removeFolder -Account 'Everyone' -AccessRights Modify
Remove-NTFSAccess -Path $removeFolder -Account 'Everyone' -AccessRights Modify -RemoveSpecific -ErrorAction Stop
(Get-Acl -LiteralPath $removeFolder).GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) |
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' } | Should -BeNullOrEmpty
}
}
}

21
Tests/Audit.Tests.ps1

@ -316,6 +316,25 @@ Describe 'Remove-NTFSAudit' {
Get-EveryoneAuditRule | Should -BeNullOrEmpty
}
It 'Should keep an audit entry that does not match exactly, given the path' {
Add-NTFSAudit -Path $removeFolder -Account 'Everyone' -AccessRights Modify
Remove-NTFSAudit -Path $removeFolder -Account 'Everyone' -AccessRights ReadData -RemoveSpecific -ErrorAction Stop
$entries = @(Get-NTFSAudit -Path $removeFolder -ExcludeInherited | Where-Object -FilterScript { $_.Account.Sid -eq 'S-1-1-0' })
$entries | Should -HaveCount 1
$entries[0].AccessRights.ToString() | Should -BeLike '*Modify*'
}
It 'Should remove an audit entry that matches exactly, given the path' {
Add-NTFSAudit -Path $removeFolder -Account 'Everyone' -AccessRights Modify
Remove-NTFSAudit -Path $removeFolder -Account 'Everyone' -AccessRights Modify -RemoveSpecific -ErrorAction Stop
Get-NTFSAudit -Path $removeFolder -ExcludeInherited | Where-Object -FilterScript { $_.Account.Sid -eq 'S-1-1-0' } |
Should -BeNullOrEmpty
}
}
Context 'With -PassThru' {
@ -415,6 +434,8 @@ Describe 'Clear-NTFSAudit' {
)
$audit.SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit') | Should -BeNullOrEmpty
$inheritedCount = @((Get-Acl -LiteralPath $file).GetAccessRules($false, $true, $sidType)).Count
# Without inherited entries, the test couldn't see them copied as explicit ones (#110).
$inheritedCount | Should -BeGreaterThan 0
Clear-NTFSAudit -Path $file -ErrorVariable clearErrors -ErrorAction SilentlyContinue

2
Tests/Inheritance.Tests.ps1

@ -127,6 +127,8 @@ Describe 'Set-NTFSInheritance' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'KeepAccess'
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
$inheritedCount = @((Get-Acl -LiteralPath $file).Access | Where-Object -Property IsInherited).Count
# Without inherited entries, the test couldn't see them kept as explicit ones (#110).
$inheritedCount | Should -BeGreaterThan 0
Set-NTFSInheritance -Path $file -AccessInheritanceEnabled $false

23
Tests/ItemCmdlets.Tests.ps1

@ -160,6 +160,20 @@ Describe 'Copy-Item2, Move-Item2, and Remove-Item2 with several paths' {
$messages.Message | Should -Contain ("File '{0}' {1} to '{2}'" -f $first, $Verb, $target)
}
It '<Command> should name the destination of a folder in the verbose message' -ForEach @(
@{ Command = 'Copy-Item2'; Verb = 'copied' }
@{ Command = 'Move-Item2'; Verb = 'moved' }
) {
$sourceFolder = Join-Path -Path $folder -ChildPath 'VerboseFolder'
Assert-TestSandboxPath -Sandbox $sandbox -Path $sourceFolder
New-Item -ItemType Directory -Path $sourceFolder | Out-Null
$target = Join-Path -Path $destination -ChildPath 'VerboseFolder'
$messages = & $Command -Path $sourceFolder -Destination $destination -Verbose 4>&1
$messages.Message | Should -Contain ("Directory '{0}' {1} to '{2}'" -f $sourceFolder, $Verb, $target)
}
# With -PassThru, both cmdlets return the item at the destination, as their pages say.
It 'Copy-Item2 -PassThru should return the copy' {
$result = Copy-Item2 -Path $first -Destination $destination -PassThru $true
@ -381,14 +395,9 @@ Describe 'Test-Path2' {
$long = Join-Path -Path $longRoot -ChildPath (('A' * 100), ('B' * 100), ('C' * 100) -join '\')
Add-Type -Path (Join-Path -Path (Get-Module -Name NTFSSecurity).ModuleBase -ChildPath 'AlphaFS.dll')
[Alphaleonis.Win32.Filesystem.Directory]::CreateDirectory($long) | Out-Null
try {
$long.Length | Should -BeGreaterThan 260
$long.Length | Should -BeGreaterThan 260
Test-Path2 -Path $long -PathType Container -ErrorAction Stop | Should -BeTrue
} finally {
# Remove-TestSandbox can't delete paths longer than 260 characters (#110).
[Alphaleonis.Win32.Filesystem.Directory]::Delete($longRoot, $true)
}
Test-Path2 -Path $long -PathType Container -ErrorAction Stop | Should -BeTrue
}
# Before 5.0.0-rc6, a path with a character that Windows doesn't allow in file names stopped the cmdlet with a

22
Tests/OutputTypes.Tests.ps1

@ -56,20 +56,36 @@ Describe 'Declared output types' {
$result = Get-FileHash2 -Path $file
$result.PSObject.TypeNames[0] | Should -Be @((Get-Command -Name Get-FileHash2).OutputType.Name)[0]
$result.PSObject.TypeNames[0] | Should -BeExactly @((Get-Command -Name Get-FileHash2).OutputType.Name)[0]
}
}
Describe 'Privilege cmdlets with -PassThru' {
BeforeAll {
# The tests enable and disable the privileges of the test process; AfterAll restores the states they had (#110).
$fileSystemPrivileges = 'TakeOwnership', 'Restore', 'Backup', 'Security'
$enabledBefore = @(Get-Privileges | Where-Object -FilterScript {
$_.Privilege.ToString() -in $fileSystemPrivileges -and $_.PrivilegeState -eq 'Enabled'
} | ForEach-Object -Process { $_.Privilege })
}
AfterEach {
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
# Before 5.0.0, -PassThru wrote the privileges as one collection.
AfterAll {
$process = [System.Diagnostics.Process]::GetCurrentProcess()
foreach ($privilege in $enabledBefore) {
$null = [ProcessPrivileges.ProcessExtensions]::EnablePrivilege($process, $privilege)
}
}
# Before 5.0.0, -PassThru wrote the privileges as one collection. The access token of a basic user holds one
# privilege only, so the test compares with the privileges that the token holds.
It 'Enable-Privileges should write one object per privilege' {
$result = @(Enable-Privileges -PassThru -ErrorAction SilentlyContinue)
$result.Count | Should -BeGreaterThan 1
$result | Should -HaveCount @(Get-Privileges).Count
$result | ForEach-Object -Process { $_ | Should -BeOfType [ProcessPrivileges.PrivilegeAndAttributes] }
}

1
Tests/SecurityDescriptorSets.Tests.ps1

@ -53,6 +53,7 @@ Describe 'Cmdlets that change a security descriptor in memory' {
It 'Disable-NTFSAccessInheritance should protect the DACL of the descriptor and keep the inherited entries' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'DisableAccess'
$inheritedCount = @((Get-Acl -LiteralPath $file).GetAccessRules($false, $true, $sidType)).Count
$inheritedCount | Should -BeGreaterThan 0
$sd = Get-NTFSSecurityDescriptor -Path $file
Disable-NTFSAccessInheritance -SecurityDescriptor $sd -ErrorAction Stop

22
Tests/TestHelpers.Tests.ps1

@ -134,6 +134,28 @@ Describe 'Test helpers' {
It 'Should not change the ACL of the target of a junction' {
(Get-Acl -LiteralPath $target).Sddl | Should -BeExactly $targetSddl
}
# Before 5.0.0-rc6, the teardown couldn't remove paths longer than 260 characters in Windows PowerShell (#110).
It 'Should remove a sandbox with a path longer than 260 characters' {
$longSandbox = New-TestSandbox -Name 'Helpers'
$long = Join-Path -Path $longSandbox -ChildPath (('A' * 100), ('B' * 100), ('C' * 100) -join '\')
Assert-TestSandboxPath -Sandbox $longSandbox -Path $long
# The \\?\ prefix lets Windows PowerShell create the path.
[IO.Directory]::CreateDirectory('\\?\' + $long) | Out-Null
[IO.File]::WriteAllText(('\\?\' + $long + '\File.txt'), 'Long')
$long.Length | Should -BeGreaterThan 260
Remove-TestSandbox -Sandbox $longSandbox
$longSandbox | Should -Not -Exist
}
# Before 5.0.0-rc6, an AfterAll after a failed setup stopped with a binding error that hid the error of the setup.
It 'Should do nothing for a sandbox that a failed setup did not create: <_>' -ForEach @('$null', 'empty string') {
$value = if ($_ -eq '$null') { $null } else { '' }
{ Remove-TestSandbox -Sandbox $value } | Should -Not -Throw
}
}
Context 'Set-TestOwner' {

37
Tests/TestHelpers.psm1

@ -87,10 +87,18 @@ function Remove-TestSandbox {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[AllowNull()]
[AllowEmptyString()]
[string]
$Sandbox
)
# A setup that failed before New-TestSandbox returned leaves nothing to remove. Before 5.0.0-rc6, the binding error
# hid the error of the setup (#110).
if ([string]::IsNullOrEmpty($Sandbox)) {
return
}
Assert-TestSandboxPath -Sandbox $Sandbox -Path $Sandbox
if (-not (Test-Path -LiteralPath $Sandbox)) {
return
@ -100,17 +108,20 @@ function Remove-TestSandbox {
# the caller uses ErrorAction Stop. Such items can still be deleted through the rights on their folder.
$ErrorActionPreference = 'Continue'
# The prefix lets .NET in Windows PowerShell reach paths longer than 260 characters (#110).
$longPathPrefix = '\\?\'
# Windows PowerShell 5.1 and icacls /T follow directory links, so the links go first. A folder that denies
# listing its content gets its own ACL reset, without /T, before it is listed.
$pending = New-Object -TypeName 'System.Collections.Generic.Stack[string]'
$pending.Push($Sandbox)
$pending.Push($longPathPrefix + $Sandbox)
while ($pending.Count -gt 0) {
$folder = $pending.Pop()
try {
$entries = [IO.Directory]::GetFileSystemEntries($folder)
}
catch {
& icacls.exe $folder /reset /C /Q *> $null
& icacls.exe $folder.Substring($longPathPrefix.Length) /reset /C /Q *> $null
$entries = [IO.Directory]::GetFileSystemEntries($folder)
}
foreach ($entry in $entries) {
@ -129,10 +140,26 @@ function Remove-TestSandbox {
}
}
& icacls.exe $Sandbox /reset /T /C /Q *> $null
Get-ChildItem -LiteralPath $Sandbox -Recurse -Force | ForEach-Object -Process {
$_.Attributes = [IO.FileAttributes]::Normal
Get-ChildItem -LiteralPath $Sandbox -Recurse -Force -ErrorAction SilentlyContinue | ForEach-Object -Process {
# Windows PowerShell returns items below paths longer than 260 characters that it can't change; rd removes them.
try {
$_.Attributes = [IO.FileAttributes]::Normal
}
catch {
Write-Verbose -Message "Keeping the attributes of '$($_.FullName)': $($_.Exception.Message)"
}
}
Remove-Item -LiteralPath $Sandbox -Recurse -Force -ErrorAction SilentlyContinue
if (Test-Path -LiteralPath $Sandbox) {
# Windows PowerShell can't remove paths longer than 260 characters; rd can with the prefix, and the links are
# gone already.
& cmd.exe /d /c ('rd /s /q "{0}{1}"' -f $longPathPrefix, $Sandbox) *> $null
}
Remove-Item -LiteralPath $Sandbox -Recurse -Force
if (Test-Path -LiteralPath $Sandbox) {
Write-Error -Message "The sandbox '$Sandbox' could not be removed."
}
try {
# Fails while another sandbox exists, also one of a test run in parallel
[IO.Directory]::Delete($script:sandboxRoot, $false)

Loading…
Cancel
Save