diff --git a/Tests/ItemCmdlets.Tests.ps1 b/Tests/ItemCmdlets.Tests.ps1 index 518a722..ea1c99f 100644 --- a/Tests/ItemCmdlets.Tests.ps1 +++ b/Tests/ItemCmdlets.Tests.ps1 @@ -207,6 +207,30 @@ Describe 'Get-ChildItem2' { ($result.Name -join ',') | Should -BeExactly 'Two.dots.txt' } + # The names are matched twice, by the enumeration and by the cmdlet, and the rules for a dot differ from those of + # Get-ChildItem, where Report.* also returns Report. The documentation lists this as a limitation; these cases pin + # it, so that a change of the rules is a decision. Report* is the control: without a dot in the pattern, the names + # without a dot are returned. + It 'Should return for -Filter ""' -ForEach @( + @{ Filter = 'Report.*'; Expected = 'Report.txt'; Outcome = 'only the names with a dot' } + @{ Filter = 'Report*'; Expected = 'Report,Report.txt'; Outcome = 'the names with and without a dot' } + @{ Filter = 'Report.'; Expected = ''; Outcome = 'nothing' } + @{ Filter = 'Rep*.'; Expected = ''; Outcome = 'nothing' } + @{ Filter = '*.'; Expected = ''; Outcome = 'nothing' } + @{ Filter = ''; Expected = ''; Outcome = 'nothing' } + ) { + $folder = New-TestSandboxItem -Sandbox $sandbox -Name 'FilterDotRules' -Directory + foreach ($name in 'Report', 'Report.txt', 'Other') { + $file = Join-Path -Path $folder -ChildPath $name + Assert-TestSandboxPath -Sandbox $sandbox -Path $file + Set-Content -LiteralPath $file -Value $name + } + + $result = @(Get-ChildItem2 -Path $folder -Filter $Filter -ErrorAction Stop) + + ($result.Name | Sort-Object) -join ',' | Should -BeExactly $Expected + } + It 'Should reject a null -Filter' { { Get-ChildItem2 -Path $tree -Filter $null -ErrorAction Stop } | Should -Throw -ErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetChildItem2' -ExpectedMessage "*'Filter'*" diff --git a/Tests/Links.Tests.ps1 b/Tests/Links.Tests.ps1 index ef2fd93..2a1908c 100644 --- a/Tests/Links.Tests.ps1 +++ b/Tests/Links.Tests.ps1 @@ -267,6 +267,8 @@ Describe 'Get-NTFSHardLink' { $readRights = 'ReadAttributes, ReadData, ReadPermissions' $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = $readRights; $currentUser = $readRights } + # Reading the data is refused. ReadAttributes and ReadPermissions are denied as well, but nothing shows that in + # every session: an elevated one was seen to read the permissions anyway. { Get-Content -LiteralPath $file -ErrorAction Stop } | Should -Throw $result = @(Get-NTFSHardLink -Path $file -ErrorVariable linkErrors -ErrorAction SilentlyContinue) diff --git a/Tests/PipelineControl.Tests.ps1 b/Tests/PipelineControl.Tests.ps1 index 32867ba..96a035c 100644 --- a/Tests/PipelineControl.Tests.ps1 +++ b/Tests/PipelineControl.Tests.ps1 @@ -288,7 +288,9 @@ BeforeAll { # The commands that write a verbose or a debug message inside the try of their loop, which the later command takes. # The first record that reaches Select-Object ends the pipeline there. The preference of the debug stream is set by - # Assert-StreamStop: the Debug switch would ask before every message. + # Assert-StreamStop: the Debug switch would ask before every message. The error action is named because the CI runner + # sets $ErrorActionPreference to Stop: a catch that reports the exception of the later command as an error of the + # item would then end the pipeline with it, and the test could not tell that catch from passing the exception on. $streamRuns = @{ 'Get-FileHash2/verbose' = @{ # The first path is a folder, which the cmdlet skips with a verbose message. @@ -297,16 +299,16 @@ BeforeAll { $context.File = New-TestSandboxItem -Sandbox $sandbox -Name 'Hashed' $context } - Run = { param ($Context) Get-FileHash2 -Path $Context.First, $Context.File -Verbose 4>&1 } + Run = { param ($Context) Get-FileHash2 -Path $Context.First, $Context.File -Verbose -ErrorAction SilentlyContinue 4>&1 } } 'Set-NTFSSecurityDescriptor/verbose' = @{ Prepare = $cases['Set-NTFSSecurityDescriptor'].Prepare - Run = { param ($Context) Set-NTFSSecurityDescriptor -SecurityDescriptor $Context.Descriptors -Verbose 4>&1 } + Run = { param ($Context) Set-NTFSSecurityDescriptor -SecurityDescriptor $Context.Descriptors -Verbose -ErrorAction SilentlyContinue 4>&1 } Untouched = $cases['Set-NTFSSecurityDescriptor'].Untouched } 'Set-NTFSOwner/debug' = @{ Prepare = { New-Pair } - Run = { param ($Context) Set-NTFSOwner -Path $Context.First, $Context.Second -Account $currentUser 5>&1 } + Run = { param ($Context) Set-NTFSOwner -Path $Context.First, $Context.Second -Account $currentUser -ErrorAction SilentlyContinue 5>&1 } } } @@ -545,7 +547,7 @@ Describe 'A later command and the error of a folder that Get-ChildItem2 cannot r $caught | Should -Not -BeNullOrEmpty $caught.FullyQualifiedErrorId | Should -BeLike 'DirUnauthorizedAccessError,*' - $caught.TargetObject | Should -BeIn $unreadable + $caught.TargetObject | Should -Be $unreadable[0] $listed | Should -BeNullOrEmpty } } diff --git a/Tests/Privileges.Tests.ps1 b/Tests/Privileges.Tests.ps1 index f07cf56..8d5e219 100644 --- a/Tests/Privileges.Tests.ps1 +++ b/Tests/Privileges.Tests.ps1 @@ -241,8 +241,10 @@ Describe 'Privileges when a later command takes the debug messages of the cmdlet # Enable-Privileges recognizes the script NTFSSecurity.Init.ps1, which a user adds to start the module, by its name: from # that script, it enables the privileges only for the module setting EnablePrivileges, from any other script always. Each -# test runs the script in a child process, which starts without enabled privileges, so that the privileges of this -# process stay as they are. +# test runs the script in a child process, which inherits the privilege states of this one (disabled here, see BeforeEach), +# so that the privileges of this process stay as they are. With the setting $true, the module enables the privileges +# itself before the cmdlet runs, so the state alone does not show that the cmdlet did: it also announces that in a verbose +# message. Describe 'Enable-Privileges in the script NTFSSecurity.Init.ps1' { BeforeAll { function Invoke-StartScript { @@ -256,24 +258,51 @@ Describe 'Enable-Privileges in the script NTFSSecurity.Init.ps1' { param ($ModulePath, $Setting) Import-Module -Name $ModulePath -ErrorAction Stop (Get-Module -Name NTFSSecurity).PrivateData['EnablePrivileges'] = ($Setting -eq 'True') -Enable-Privileges +$messages = @(Enable-Privileges -Verbose 4>&1 | ForEach-Object -Process { "$($_.Message)" }) +'ANNOUNCED:{0}' -f [bool] @($messages -like '*are now enabled giving you access*').Count 'BACKUP:{0}' -f (Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Backup').PrivilegeState '@ - $output = & (Get-Process -Id $PID).Path -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $script -ModulePath ([IO.Path]::GetFullPath($modulePath)) -Setting $Setting - @($output | Where-Object -FilterScript { $_ -like 'BACKUP:*' }) -replace '^BACKUP:' + $output = @(& (Get-Process -Id $PID).Path -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $script -ModulePath ([IO.Path]::GetFullPath($modulePath)) -Setting $Setting) + [pscustomobject]@{ + Announced = @($output | Where-Object -FilterScript { $_ -like 'ANNOUNCED:*' }) -replace '^ANNOUNCED:' + Backup = @($output | Where-Object -FilterScript { $_ -like 'BACKUP:*' }) -replace '^BACKUP:' + } } } + BeforeEach { + Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + } + + AfterEach { + Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + } + It 'Should enable the privileges when the module setting EnablePrivileges is $true' -Skip:(-not $holdsPrivileges) { - Invoke-StartScript -ScriptName 'NTFSSecurity.Init.ps1' -Setting $true | Should -Be 'Enabled' + Get-BackupPrivilegeState | Should -Be 'Disabled' + + $result = Invoke-StartScript -ScriptName 'NTFSSecurity.Init.ps1' -Setting $true + + $result.Backup | Should -Be 'Enabled' + $result.Announced | Should -Be 'True' } It 'Should leave the privileges disabled when the module setting EnablePrivileges is $false' -Skip:(-not $holdsPrivileges) { - Invoke-StartScript -ScriptName 'NTFSSecurity.Init.ps1' -Setting $false | Should -Be 'Disabled' + Get-BackupPrivilegeState | Should -Be 'Disabled' + + $result = Invoke-StartScript -ScriptName 'NTFSSecurity.Init.ps1' -Setting $false + + $result.Backup | Should -Be 'Disabled' + $result.Announced | Should -Be 'False' } It 'Should enable the privileges in a script of another name also when the module setting EnablePrivileges is $false' -Skip:(-not $holdsPrivileges) { - Invoke-StartScript -ScriptName 'Other.ps1' -Setting $false | Should -Be 'Enabled' + Get-BackupPrivilegeState | Should -Be 'Disabled' + + $result = Invoke-StartScript -ScriptName 'Other.ps1' -Setting $false + + $result.Backup | Should -Be 'Enabled' + $result.Announced | Should -Be 'True' } } diff --git a/Tests/TestHelpers.Tests.ps1 b/Tests/TestHelpers.Tests.ps1 index 32bbd74..055f6ad 100644 --- a/Tests/TestHelpers.Tests.ps1 +++ b/Tests/TestHelpers.Tests.ps1 @@ -283,6 +283,25 @@ Describe 'Test helpers' { Should -Throw -ExpectedMessage 'Refusing to change*' } + # The native call follows a link, so a junction in the sandbox that points to another folder is refused as the + # item itself, not only as a folder of its path. + It 'Should refuse an item that is a link, which can point outside the sandbox' { + $otherSandbox = New-TestSandbox -Name 'Helpers' + try { + $link = Join-Path -Path $sandbox -ChildPath 'NullDaclLink' + Assert-TestSandboxPath -Sandbox $sandbox -Path $link + New-Item -ItemType Junction -Path $link -Value $otherSandbox | Out-Null + $before = (Get-Acl -LiteralPath $otherSandbox).Sddl + + { Set-TestNullDacl -Sandbox $sandbox -Path $link } | Should -Throw -ExpectedMessage '*because it is a link*' + + (Get-Acl -LiteralPath $otherSandbox).Sddl | Should -BeExactly $before + } + finally { + Remove-TestSandbox -Sandbox $otherSandbox + } + } + It 'Should throw its own error when Windows refuses' { $missing = Join-Path -Path $sandbox -ChildPath 'Missing.txt' diff --git a/Tests/TestHelpers.psm1 b/Tests/TestHelpers.psm1 index 251a766..b7dae9e 100644 --- a/Tests/TestHelpers.psm1 +++ b/Tests/TestHelpers.psm1 @@ -364,6 +364,15 @@ function Set-TestNullDacl { ) Assert-TestSandboxPath -Sandbox $Sandbox -Path $Path + $location = (Get-Location -PSProvider FileSystem).ProviderPath + $fullName = [IO.Path]::GetFullPath([IO.Path]::Combine($location, $Path)) + # Assert-TestSandboxPath checks the folders of the path for links, not the item itself, and the native call follows a + # link: a junction to a folder outside the sandbox would give everyone every access to that folder. + $attributes = try { [IO.File]::GetAttributes($fullName) } catch { $null } + if ($null -ne $attributes -and ($attributes -band [IO.FileAttributes]::ReparsePoint)) { + throw "Refusing to change '$fullName', because it is a link." + } + if (-not ('NtfsSecurityTests.NativeAcl' -as [type])) { Add-Type -TypeDefinition @' namespace NtfsSecurityTests @@ -385,8 +394,6 @@ namespace NtfsSecurityTests '@ } - $location = (Get-Location -PSProvider FileSystem).ProviderPath - $fullName = [IO.Path]::GetFullPath([IO.Path]::Combine($location, $Path)) $result = [NtfsSecurityTests.NativeAcl]::SetNullDacl($fullName) if ($result -ne 0) { throw "SetNamedSecurityInfo could not set a NULL DACL on '$fullName' (error $result)."