test(items): assert the cmdlets that the judged matrix found untested
Add Tests\ParameterSets.Items.Tests.ps1, closing TestNeeded gaps the judged
parameter-set matrix found for Copy-Item2, Get-ChildItem2, Get-Item2,
Move-Item2, Remove-Item2, Test-Path2, Get-FileHash2, Get-DiskSpace,
New-NTFSHardLink, Get-NTFSHardLink, New-NTFSSymbolicLink, Enable-Privileges,
Disable-Privileges, and Get-Privileges:
- Pipeline input by value for Copy-Item2, Move-Item2, Get-ChildItem2,
Get-Item2, and Remove-Item2 (never exercised before), including
continuation past a missing item and AlphaFS objects piped from
Get-ChildItem2.
- An empty string, $null, a piped empty string, or an empty array element
for -Path/-Target is rejected by ValidateNotNullOrEmpty and never falls
back to the current location, for Copy-Item2, Move-Item2, Remove-Item2,
New-NTFSHardLink, and New-NTFSSymbolicLink. Verified by running against
the built module in both PowerShell editions before being pinned.
- Get-NTFSHardLink error categories, output type, and positional binding.
- Pipeline continuation (Select-Object -First 1) for New-NTFSHardLink and
New-NTFSSymbolicLink, and a successful link after an earlier failure in
the same New-NTFSSymbolicLink call.
- Enable-Privileges and Disable-Privileges error paths (SecurityError,
Security2.AdjustPriviledgeException), runnable on any token.
- Get-DiskSpace with a valid and an invalid drive letter together in either
order, and positional/lower-case binding.
- Get-Privileges compared against whoami /priv for the five privileges
that both name the same way.
- Get-FileHash2 pipeline objects with -Algorithm, and a locked path before
a valid one with its error category and a relative-path hash.
Every It asserts persisted state, an ErrorId/category/target, continuation,
or an output type; none asserts only an English message. All four runtime
configurations (elevated/basic user x Windows PowerShell 5.1/PowerShell 7)
pass with 0 failures; two symbolic-link-privilege-gated tests skip on the
basic-user configurations and run on both elevated ones. Mutation testing
against 8 one-statement changes in a separate, discarded worktree confirmed
each targeted test fails for the right reason; production sources were
restored exactly after every mutation and no production code changed here.
Does not pin the Get-ChildItem2 -Filter dot rules, the Decision 22 link and
move/copy behaviors, the PrivilegeEnabler class, or the owner-restore
handlers. A probe of Get-NTFSHardLink and New-NTFSHardLink -PassThru
through a subst-mapped drive found a pre-existing defect (reported
separately, not fixed or pinned here): both combine the apparent drive
root with the volume-relative hard-link names from Windows, which throws
FileNotFoundException once the drive's root differs from the real NTFS
volume root.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
<#
Closes TestNeeded gaps that the judged parameter-set matrix found in the item , link , hash , privilege , and
disk-space cmdlets of the module built in NTFSSecurity \ bin \ Release : no test piped into Copy-Item2 , Get-ChildItem2 ,
Get-Item2 , Move-Item2 , or Remove-Item2 ; an empty or null -Path / -Target never falls back to the current location ;
missing error categories and output types ; and continuation after an earlier item in the same pipeline . It does not
pin the -Filter dot rules , the Decision 22 behaviors , the PrivilegeEnabler class , or the owner-restore handlers ,
which are covered and decided elsewhere .
#>
[ Diagnostics . CodeAnalysis . SuppressMessageAttribute (
'PSUseDeclaredVarsMoreThanAssignments' , '' , Justification = 'Pester shares variables between blocks.'
) ]
param ( )
BeforeDiscovery {
Import-Module -Name ( Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1' ) -Force
$canCreateSymbolicLinks = Test-PrivilegeHeld -Name 'SeCreateSymbolicLinkPrivilege'
}
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 'ParameterSetsItems'
Push-Location -LiteralPath $sandbox
}
AfterAll {
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'Copy-Item2 and Move-Item2 accept pipeline input' {
# The cmdlet pages document piping Get-ChildItem2 output into Copy-Item2/Move-Item2, but no existing test pipes
# anything into either cmdlet. A missing item in the middle of the pipeline must not stop the surrounding items.
It '<Command> should accept <InputKind> piped by value and still process the surrounding items' -ForEach @ (
@ { Command = 'Copy-Item2' ; InputKind = 'plain path strings with a missing item in the middle' }
@ { Command = 'Copy-Item2' ; InputKind = 'Get-ChildItem2 file objects' }
@ { Command = 'Move-Item2' ; InputKind = 'plain path strings with a missing item in the middle' }
@ { Command = 'Move-Item2' ; InputKind = 'Get-ChildItem2 file objects' }
) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'PipelineItems' -Directory
$destination = Join-Path -Path $folder -ChildPath 'Destination'
$first = Join-Path -Path $folder -ChildPath 'First.txt'
$second = Join-Path -Path $folder -ChildPath 'Second.txt'
$missing = Join-Path -Path $folder -ChildPath 'Missing.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $destination , $first , $second , $missing
New-Item -ItemType Directory -Path $destination | Out-Null
Set-Content -LiteralPath $first -Value 'First'
Set-Content -LiteralPath $second -Value 'Second'
$usesMissingItem = $InputKind -like '*missing item*'
$items = if ( $usesMissingItem ) {
@ ( $first , $missing , $second )
}
else {
@ ( Get-ChildItem2 -Path $folder -File -ErrorAction Stop )
}
$result = @ ( $items | & $Command -Destination $destination -PassThru $true -ErrorVariable itemErrors -ErrorAction SilentlyContinue )
if ( $usesMissingItem ) {
$itemErrors | Should -HaveCount 1
$itemErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'FileNotFound,*'
$itemErrors [ 0 ] . CategoryInfo . Category | Should -Be 'ObjectNotFound'
$itemErrors [ 0 ] . TargetObject | Should -Be $missing
$itemErrors [ 0 ] . Exception | Should -BeOfType [System.IO.FileNotFoundException]
}
else {
$itemErrors | Should -BeNullOrEmpty
}
$result | Should -HaveCount 2
$destinationFirst = Join-Path -Path $destination -ChildPath 'First.txt'
$destinationSecond = Join-Path -Path $destination -ChildPath 'Second.txt'
@ ( $result . FullName | Sort-Object ) | Should -Be ( @ ( $destinationFirst , $destinationSecond ) | Sort-Object )
Get-Content -LiteralPath $destinationFirst | Should -Be 'First'
Get-Content -LiteralPath $destinationSecond | Should -Be 'Second'
if ( $Command -eq 'Copy-Item2' ) {
$first | Should -Exist
$second | Should -Exist
}
else {
$first | Should -Not -Exist
$second | Should -Not -Exist
}
}
}
Describe 'Get-ChildItem2 accepts pipeline input' {
# No existing test pipes a path into Get-ChildItem2, although -Path documents taking it by value and by FullName.
It 'Should accept piped folders by value, write one error for a path that does not exist, and list the items of the other folders' {
$folderA = New-TestSandboxItem -Sandbox $sandbox -Name 'PipelineFolderA' -Directory
$folderB = New-TestSandboxItem -Sandbox $sandbox -Name 'PipelineFolderB' -Directory
$missing = Join-Path -Path $sandbox -ChildPath ( 'PipelineChildMissing-{0}' -f [guid] :: NewGuid ( ) . ToString ( 'N' ) . Substring ( 0 , 8 ) )
$fileA = Join-Path -Path $folderA -ChildPath 'A.txt'
$fileB = Join-Path -Path $folderB -ChildPath 'B.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $fileA , $fileB
Set-Content -LiteralPath $fileA -Value 'A'
Set-Content -LiteralPath $fileB -Value 'B'
$result = @ ( @ ( $folderA , $missing , $folderB ) | Get-ChildItem2 -ErrorVariable childErrors -ErrorAction SilentlyContinue )
$childErrors | Should -HaveCount 1
$childErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'FileNotFound,*'
$childErrors [ 0 ] . CategoryInfo . Category | Should -Be 'ObjectNotFound'
$childErrors [ 0 ] . TargetObject | Should -Be $missing
@ ( $result . FullName | Sort-Object ) | Should -Be ( @ ( $fileA , $fileB ) | Sort-Object )
}
# Chains a folder object from one call into -Path of a second call, by value (AlphaFS objects, not strings).
It 'Should accept folder objects piped from Get-ChildItem2 -Directory and list only the files directly below them' {
$tree = New-TestSandboxItem -Sandbox $sandbox -Name 'ChainedTree' -Directory
$childFolder = Join-Path -Path $tree -ChildPath 'Child'
$childFile = Join-Path -Path $childFolder -ChildPath 'Child.log'
$grandchildFile = Join-Path -Path $childFolder -ChildPath 'Grandchild\Deep.log'
$rootFile = Join-Path -Path $tree -ChildPath 'Root.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $childFile , $grandchildFile , $rootFile
New-Item -ItemType Directory -Path ( Split-Path -Path $grandchildFile -Parent ) -Force | Out-Null
Set-Content -LiteralPath $childFile -Value 'Child'
Set-Content -LiteralPath $grandchildFile -Value 'Grandchild'
Set-Content -LiteralPath $rootFile -Value 'Root'
$result = @ ( Get-ChildItem2 -Path $tree -Directory -ErrorAction Stop | Get-ChildItem2 -File -ErrorAction Stop )
$result | Should -HaveCount 1
$result [ 0 ] | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
$result [ 0 ] . FullName | Should -Be $childFile
}
}
Describe 'Get-Item2 accepts pipeline input' {
# No existing test pipes a path into Get-Item2; the output type and Mode were asserted only for non-piped calls.
It 'Should accept piped items by value, return each in order with its output type, and write one error for a path that does not exist' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'GetItemPipelineFile'
[IO.File] :: SetAttributes ( $file , [IO.FileAttributes] :: Archive )
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'GetItemPipelineFolder' -Directory
$missing = Join-Path -Path $sandbox -ChildPath ( 'GetItemPipelineMissing-{0}' -f [guid] :: NewGuid ( ) . ToString ( 'N' ) . Substring ( 0 , 8 ) )
$result = @ ( @ ( $file , $missing , $folder ) | Get-Item2 -ErrorVariable itemErrors -ErrorAction SilentlyContinue )
$itemErrors | Should -HaveCount 1
$itemErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'FileNotFound,*'
$itemErrors [ 0 ] . CategoryInfo . Category | Should -Be 'ObjectNotFound'
$itemErrors [ 0 ] . TargetObject | Should -Be $missing
$result | Should -HaveCount 2
$result [ 0 ] | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
$result [ 0 ] . FullName | Should -Be $file
$result [ 0 ] . Mode | Should -BeExactly '-a---'
$result [ 1 ] | Should -BeOfType [Alphaleonis.Win32.Filesystem.DirectoryInfo]
$result [ 1 ] . FullName | Should -Be $folder
$result [ 1 ] . Mode | Should -BeExactly 'd----'
}
}
Describe 'Remove-Item2 accepts pipeline input' {
# No existing test pipes a path into Remove-Item2, although its page documents Get-ChildItem2 | Remove-Item2.
It 'Should accept piped items by value, write one error for a non-empty folder, remove the files, and keep the folder' {
$fileA = New-TestSandboxItem -Sandbox $sandbox -Name 'RemovePipelineA'
$fileB = New-TestSandboxItem -Sandbox $sandbox -Name 'RemovePipelineB'
$nonEmptyFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'RemovePipelineFolder' -Directory
$kept = Join-Path -Path $nonEmptyFolder -ChildPath 'Keep.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $kept
Set-Content -LiteralPath $kept -Value 'Keep'
$result = @ ( @ ( $fileA , $nonEmptyFolder , $fileB ) | Remove-Item2 -PassThru -ErrorVariable removeErrors -ErrorAction SilentlyContinue )
$removeErrors | Should -HaveCount 1
$removeErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'DeleteError,*'
$removeErrors [ 0 ] . CategoryInfo . Category | Should -Be 'InvalidData'
$removeErrors [ 0 ] . TargetObject | Should -Be $nonEmptyFolder
$fileA | Should -Not -Exist
$fileB | Should -Not -Exist
$nonEmptyFolder | Should -Exist
Get-Content -LiteralPath $kept | Should -Be 'Keep'
$result | Should -HaveCount 2
@ ( $result . FullName | Sort-Object ) | Should -Be ( @ ( $fileA , $fileB ) | Sort-Object )
}
}
Describe 'Empty or null -Path or -Target is rejected instead of falling back to the current location' {
# GetRelativePath('') returns the current location, so ValidateNotNullOrEmpty is the only guard between an empty
# value and a recursive delete of the current folder. A probe against the built module confirmed in both editions
# that the attribute does reject an empty string, $null, a piped empty string, and an empty array element, so this
# pins verified-safe behavior rather than an assumption.
It 'Remove-Item2 should reject -Path <CaseName> instead of removing the current location' -ForEach @ (
@ { CaseName = 'as an empty string' }
@ { CaseName = 'as $null' }
@ { CaseName = 'as an array with an empty element' }
@ { CaseName = 'piped as an empty string' }
) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'EmptyPathGuard' -Directory
$keep = Join-Path -Path $folder -ChildPath 'Keep.txt'
$innerFolder = Join-Path -Path $folder -ChildPath 'Sub'
$inner = Join-Path -Path $innerFolder -ChildPath 'Inner.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $keep , $innerFolder , $inner
New-Item -ItemType Directory -Path $innerFolder | Out-Null
Set-Content -LiteralPath $keep -Value 'Keep'
Set-Content -LiteralPath $inner -Value 'Inner'
Push-Location -LiteralPath $folder
try {
switch ( $CaseName ) {
'as an empty string' {
{ Remove-Item2 -Path '' -Recurse -Force -ErrorAction Stop } |
Should -Throw -ErrorId 'ParameterArgumentValidationError,NTFSSecurity.RemoveItem2'
}
'as $null' {
{ Remove-Item2 -Path $null -Recurse -Force -ErrorAction Stop } |
Should -Throw -ErrorId 'ParameterArgumentValidationError,NTFSSecurity.RemoveItem2'
}
'as an array with an empty element' {
{ Remove-Item2 -Path @ ( '' ) -Recurse -Force -ErrorAction Stop } |
Should -Throw -ErrorId 'ParameterArgumentValidationError,NTFSSecurity.RemoveItem2'
}
'piped as an empty string' {
{ '' | Remove-Item2 -Recurse -Force -ErrorAction Stop } |
Should -Throw -ErrorId 'ParameterArgumentValidationError,NTFSSecurity.RemoveItem2'
}
}
}
finally {
Pop-Location
}
$keep | Should -Exist
$inner | Should -Exist
}
It '<Command> should reject -Path <CaseName> instead of silently using the current location' -ForEach @ (
@ { Command = 'Copy-Item2' ; TypeName = 'NTFSSecurity.CopyItem2' ; CaseName = 'as an empty string' }
@ { Command = 'Copy-Item2' ; TypeName = 'NTFSSecurity.CopyItem2' ; CaseName = 'as $null' }
@ { Command = 'Copy-Item2' ; TypeName = 'NTFSSecurity.CopyItem2' ; CaseName = 'as an array with an empty element' }
@ { Command = 'Move-Item2' ; TypeName = 'NTFSSecurity.MoveItem2' ; CaseName = 'as an empty string' }
@ { Command = 'Move-Item2' ; TypeName = 'NTFSSecurity.MoveItem2' ; CaseName = 'as $null' }
@ { Command = 'Move-Item2' ; TypeName = 'NTFSSecurity.MoveItem2' ; CaseName = 'as an array with an empty element' }
) {
$destination = New-TestSandboxItem -Sandbox $sandbox -Name 'EmptyPathDestination' -Directory
$source = New-TestSandboxItem -Sandbox $sandbox -Name 'EmptyPathSource'
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
# An empty path resolves to the current location, so the rows run in a folder of their own: a guard that regressed
# would copy or move this empty folder and not the whole sandbox into itself.
$workFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'EmptyPathWork' -Directory
test(items): assert the cmdlets that the judged matrix found untested
Add Tests\ParameterSets.Items.Tests.ps1, closing TestNeeded gaps the judged
parameter-set matrix found for Copy-Item2, Get-ChildItem2, Get-Item2,
Move-Item2, Remove-Item2, Test-Path2, Get-FileHash2, Get-DiskSpace,
New-NTFSHardLink, Get-NTFSHardLink, New-NTFSSymbolicLink, Enable-Privileges,
Disable-Privileges, and Get-Privileges:
- Pipeline input by value for Copy-Item2, Move-Item2, Get-ChildItem2,
Get-Item2, and Remove-Item2 (never exercised before), including
continuation past a missing item and AlphaFS objects piped from
Get-ChildItem2.
- An empty string, $null, a piped empty string, or an empty array element
for -Path/-Target is rejected by ValidateNotNullOrEmpty and never falls
back to the current location, for Copy-Item2, Move-Item2, Remove-Item2,
New-NTFSHardLink, and New-NTFSSymbolicLink. Verified by running against
the built module in both PowerShell editions before being pinned.
- Get-NTFSHardLink error categories, output type, and positional binding.
- Pipeline continuation (Select-Object -First 1) for New-NTFSHardLink and
New-NTFSSymbolicLink, and a successful link after an earlier failure in
the same New-NTFSSymbolicLink call.
- Enable-Privileges and Disable-Privileges error paths (SecurityError,
Security2.AdjustPriviledgeException), runnable on any token.
- Get-DiskSpace with a valid and an invalid drive letter together in either
order, and positional/lower-case binding.
- Get-Privileges compared against whoami /priv for the five privileges
that both name the same way.
- Get-FileHash2 pipeline objects with -Algorithm, and a locked path before
a valid one with its error category and a relative-path hash.
Every It asserts persisted state, an ErrorId/category/target, continuation,
or an output type; none asserts only an English message. All four runtime
configurations (elevated/basic user x Windows PowerShell 5.1/PowerShell 7)
pass with 0 failures; two symbolic-link-privilege-gated tests skip on the
basic-user configurations and run on both elevated ones. Mutation testing
against 8 one-statement changes in a separate, discarded worktree confirmed
each targeted test fails for the right reason; production sources were
restored exactly after every mutation and no production code changed here.
Does not pin the Get-ChildItem2 -Filter dot rules, the Decision 22 link and
move/copy behaviors, the PrivilegeEnabler class, or the owner-restore
handlers. A probe of Get-NTFSHardLink and New-NTFSHardLink -PassThru
through a subst-mapped drive found a pre-existing defect (reported
separately, not fixed or pinned here): both combine the apparent drive
root with the volume-relative hard-link names from Windows, which throws
FileNotFoundException once the drive's root differs from the real NTFS
volume root.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
# The comma keeps the array: a switch writes to the pipeline, which unrolls @('') into a plain empty string.
test(items): assert the cmdlets that the judged matrix found untested
Add Tests\ParameterSets.Items.Tests.ps1, closing TestNeeded gaps the judged
parameter-set matrix found for Copy-Item2, Get-ChildItem2, Get-Item2,
Move-Item2, Remove-Item2, Test-Path2, Get-FileHash2, Get-DiskSpace,
New-NTFSHardLink, Get-NTFSHardLink, New-NTFSSymbolicLink, Enable-Privileges,
Disable-Privileges, and Get-Privileges:
- Pipeline input by value for Copy-Item2, Move-Item2, Get-ChildItem2,
Get-Item2, and Remove-Item2 (never exercised before), including
continuation past a missing item and AlphaFS objects piped from
Get-ChildItem2.
- An empty string, $null, a piped empty string, or an empty array element
for -Path/-Target is rejected by ValidateNotNullOrEmpty and never falls
back to the current location, for Copy-Item2, Move-Item2, Remove-Item2,
New-NTFSHardLink, and New-NTFSSymbolicLink. Verified by running against
the built module in both PowerShell editions before being pinned.
- Get-NTFSHardLink error categories, output type, and positional binding.
- Pipeline continuation (Select-Object -First 1) for New-NTFSHardLink and
New-NTFSSymbolicLink, and a successful link after an earlier failure in
the same New-NTFSSymbolicLink call.
- Enable-Privileges and Disable-Privileges error paths (SecurityError,
Security2.AdjustPriviledgeException), runnable on any token.
- Get-DiskSpace with a valid and an invalid drive letter together in either
order, and positional/lower-case binding.
- Get-Privileges compared against whoami /priv for the five privileges
that both name the same way.
- Get-FileHash2 pipeline objects with -Algorithm, and a locked path before
a valid one with its error category and a relative-path hash.
Every It asserts persisted state, an ErrorId/category/target, continuation,
or an output type; none asserts only an English message. All four runtime
configurations (elevated/basic user x Windows PowerShell 5.1/PowerShell 7)
pass with 0 failures; two symbolic-link-privilege-gated tests skip on the
basic-user configurations and run on both elevated ones. Mutation testing
against 8 one-statement changes in a separate, discarded worktree confirmed
each targeted test fails for the right reason; production sources were
restored exactly after every mutation and no production code changed here.
Does not pin the Get-ChildItem2 -Filter dot rules, the Decision 22 link and
move/copy behaviors, the PrivilegeEnabler class, or the owner-restore
handlers. A probe of Get-NTFSHardLink and New-NTFSHardLink -PassThru
through a subst-mapped drive found a pre-existing defect (reported
separately, not fixed or pinned here): both combine the apparent drive
root with the volume-relative hard-link names from Windows, which throws
FileNotFoundException once the drive's root differs from the real NTFS
volume root.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$badPath = switch ( $CaseName ) {
'as an empty string' { '' }
'as $null' { $null }
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
'as an array with an empty element' { , @ ( '' ) }
test(items): assert the cmdlets that the judged matrix found untested
Add Tests\ParameterSets.Items.Tests.ps1, closing TestNeeded gaps the judged
parameter-set matrix found for Copy-Item2, Get-ChildItem2, Get-Item2,
Move-Item2, Remove-Item2, Test-Path2, Get-FileHash2, Get-DiskSpace,
New-NTFSHardLink, Get-NTFSHardLink, New-NTFSSymbolicLink, Enable-Privileges,
Disable-Privileges, and Get-Privileges:
- Pipeline input by value for Copy-Item2, Move-Item2, Get-ChildItem2,
Get-Item2, and Remove-Item2 (never exercised before), including
continuation past a missing item and AlphaFS objects piped from
Get-ChildItem2.
- An empty string, $null, a piped empty string, or an empty array element
for -Path/-Target is rejected by ValidateNotNullOrEmpty and never falls
back to the current location, for Copy-Item2, Move-Item2, Remove-Item2,
New-NTFSHardLink, and New-NTFSSymbolicLink. Verified by running against
the built module in both PowerShell editions before being pinned.
- Get-NTFSHardLink error categories, output type, and positional binding.
- Pipeline continuation (Select-Object -First 1) for New-NTFSHardLink and
New-NTFSSymbolicLink, and a successful link after an earlier failure in
the same New-NTFSSymbolicLink call.
- Enable-Privileges and Disable-Privileges error paths (SecurityError,
Security2.AdjustPriviledgeException), runnable on any token.
- Get-DiskSpace with a valid and an invalid drive letter together in either
order, and positional/lower-case binding.
- Get-Privileges compared against whoami /priv for the five privileges
that both name the same way.
- Get-FileHash2 pipeline objects with -Algorithm, and a locked path before
a valid one with its error category and a relative-path hash.
Every It asserts persisted state, an ErrorId/category/target, continuation,
or an output type; none asserts only an English message. All four runtime
configurations (elevated/basic user x Windows PowerShell 5.1/PowerShell 7)
pass with 0 failures; two symbolic-link-privilege-gated tests skip on the
basic-user configurations and run on both elevated ones. Mutation testing
against 8 one-statement changes in a separate, discarded worktree confirmed
each targeted test fails for the right reason; production sources were
restored exactly after every mutation and no production code changed here.
Does not pin the Get-ChildItem2 -Filter dot rules, the Decision 22 link and
move/copy behaviors, the PrivilegeEnabler class, or the owner-restore
handlers. A probe of Get-NTFSHardLink and New-NTFSHardLink -PassThru
through a subst-mapped drive found a pre-existing defect (reported
separately, not fixed or pinned here): both combine the apparent drive
root with the volume-relative hard-link names from Windows, which throws
FileNotFoundException once the drive's root differs from the real NTFS
volume root.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
}
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
Push-Location -LiteralPath $workFolder
try {
{ & $Command -Path $badPath -Destination $destination -ErrorAction Stop } |
Should -Throw -ErrorId " ParameterArgumentValidationError, $ TypeName "
}
finally {
Pop-Location
}
test(items): assert the cmdlets that the judged matrix found untested
Add Tests\ParameterSets.Items.Tests.ps1, closing TestNeeded gaps the judged
parameter-set matrix found for Copy-Item2, Get-ChildItem2, Get-Item2,
Move-Item2, Remove-Item2, Test-Path2, Get-FileHash2, Get-DiskSpace,
New-NTFSHardLink, Get-NTFSHardLink, New-NTFSSymbolicLink, Enable-Privileges,
Disable-Privileges, and Get-Privileges:
- Pipeline input by value for Copy-Item2, Move-Item2, Get-ChildItem2,
Get-Item2, and Remove-Item2 (never exercised before), including
continuation past a missing item and AlphaFS objects piped from
Get-ChildItem2.
- An empty string, $null, a piped empty string, or an empty array element
for -Path/-Target is rejected by ValidateNotNullOrEmpty and never falls
back to the current location, for Copy-Item2, Move-Item2, Remove-Item2,
New-NTFSHardLink, and New-NTFSSymbolicLink. Verified by running against
the built module in both PowerShell editions before being pinned.
- Get-NTFSHardLink error categories, output type, and positional binding.
- Pipeline continuation (Select-Object -First 1) for New-NTFSHardLink and
New-NTFSSymbolicLink, and a successful link after an earlier failure in
the same New-NTFSSymbolicLink call.
- Enable-Privileges and Disable-Privileges error paths (SecurityError,
Security2.AdjustPriviledgeException), runnable on any token.
- Get-DiskSpace with a valid and an invalid drive letter together in either
order, and positional/lower-case binding.
- Get-Privileges compared against whoami /priv for the five privileges
that both name the same way.
- Get-FileHash2 pipeline objects with -Algorithm, and a locked path before
a valid one with its error category and a relative-path hash.
Every It asserts persisted state, an ErrorId/category/target, continuation,
or an output type; none asserts only an English message. All four runtime
configurations (elevated/basic user x Windows PowerShell 5.1/PowerShell 7)
pass with 0 failures; two symbolic-link-privilege-gated tests skip on the
basic-user configurations and run on both elevated ones. Mutation testing
against 8 one-statement changes in a separate, discarded worktree confirmed
each targeted test fails for the right reason; production sources were
restored exactly after every mutation and no production code changed here.
Does not pin the Get-ChildItem2 -Filter dot rules, the Decision 22 link and
move/copy behaviors, the PrivilegeEnabler class, or the owner-restore
handlers. A probe of Get-NTFSHardLink and New-NTFSHardLink -PassThru
through a subst-mapped drive found a pre-existing defect (reported
separately, not fixed or pinned here): both combine the apparent drive
root with the volume-relative hard-link names from Windows, which throws
FileNotFoundException once the drive's root differs from the real NTFS
volume root.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$source | Should -Exist
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$workFolder | Should -Exist
test(items): assert the cmdlets that the judged matrix found untested
Add Tests\ParameterSets.Items.Tests.ps1, closing TestNeeded gaps the judged
parameter-set matrix found for Copy-Item2, Get-ChildItem2, Get-Item2,
Move-Item2, Remove-Item2, Test-Path2, Get-FileHash2, Get-DiskSpace,
New-NTFSHardLink, Get-NTFSHardLink, New-NTFSSymbolicLink, Enable-Privileges,
Disable-Privileges, and Get-Privileges:
- Pipeline input by value for Copy-Item2, Move-Item2, Get-ChildItem2,
Get-Item2, and Remove-Item2 (never exercised before), including
continuation past a missing item and AlphaFS objects piped from
Get-ChildItem2.
- An empty string, $null, a piped empty string, or an empty array element
for -Path/-Target is rejected by ValidateNotNullOrEmpty and never falls
back to the current location, for Copy-Item2, Move-Item2, Remove-Item2,
New-NTFSHardLink, and New-NTFSSymbolicLink. Verified by running against
the built module in both PowerShell editions before being pinned.
- Get-NTFSHardLink error categories, output type, and positional binding.
- Pipeline continuation (Select-Object -First 1) for New-NTFSHardLink and
New-NTFSSymbolicLink, and a successful link after an earlier failure in
the same New-NTFSSymbolicLink call.
- Enable-Privileges and Disable-Privileges error paths (SecurityError,
Security2.AdjustPriviledgeException), runnable on any token.
- Get-DiskSpace with a valid and an invalid drive letter together in either
order, and positional/lower-case binding.
- Get-Privileges compared against whoami /priv for the five privileges
that both name the same way.
- Get-FileHash2 pipeline objects with -Algorithm, and a locked path before
a valid one with its error category and a relative-path hash.
Every It asserts persisted state, an ErrorId/category/target, continuation,
or an output type; none asserts only an English message. All four runtime
configurations (elevated/basic user x Windows PowerShell 5.1/PowerShell 7)
pass with 0 failures; two symbolic-link-privilege-gated tests skip on the
basic-user configurations and run on both elevated ones. Mutation testing
against 8 one-statement changes in a separate, discarded worktree confirmed
each targeted test fails for the right reason; production sources were
restored exactly after every mutation and no production code changed here.
Does not pin the Get-ChildItem2 -Filter dot rules, the Decision 22 link and
move/copy behaviors, the PrivilegeEnabler class, or the owner-restore
handlers. A probe of Get-NTFSHardLink and New-NTFSHardLink -PassThru
through a subst-mapped drive found a pre-existing defect (reported
separately, not fixed or pinned here): both combine the apparent drive
root with the volume-relative hard-link names from Windows, which throws
FileNotFoundException once the drive's root differs from the real NTFS
volume root.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
@ ( Get-ChildItem -LiteralPath $destination -Force ) | Should -BeNullOrEmpty
}
# "Both link creators" per the proposal; a probe confirmed the same safe, verified behavior for New-NTFSHardLink.
It '<Command> should reject <CaseName> instead of creating a link to the current folder' -ForEach @ (
@ { Command = 'New-NTFSHardLink' ; TypeName = 'NTFSSecurity.NewHardLink' ; CaseName = 'an empty -Target' }
@ { Command = 'New-NTFSHardLink' ; TypeName = 'NTFSSecurity.NewHardLink' ; CaseName = 'a $null -Target' }
@ { Command = 'New-NTFSHardLink' ; TypeName = 'NTFSSecurity.NewHardLink' ; CaseName = 'an empty -Path' }
@ { Command = 'New-NTFSSymbolicLink' ; TypeName = 'NTFSSecurity.NewSymbolicLink' ; CaseName = 'an empty -Target' }
@ { Command = 'New-NTFSSymbolicLink' ; TypeName = 'NTFSSecurity.NewSymbolicLink' ; CaseName = 'a $null -Target' }
@ { Command = 'New-NTFSSymbolicLink' ; TypeName = 'NTFSSecurity.NewSymbolicLink' ; CaseName = 'an empty -Path' }
) {
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'EmptyGuardTarget'
$link = Join-Path -Path $sandbox -ChildPath ( 'EmptyGuardLink-{0}' -f [guid] :: NewGuid ( ) . ToString ( 'N' ) . Substring ( 0 , 8 ) )
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
switch ( $CaseName ) {
'an empty -Target' {
{ & $Command -Path $link -Target '' -ErrorAction Stop } |
Should -Throw -ErrorId " ParameterArgumentValidationError, $ TypeName "
$link | Should -Not -Exist
}
'a $null -Target' {
{ & $Command -Path $link -Target $null -ErrorAction Stop } |
Should -Throw -ErrorId " ParameterArgumentValidationError, $ TypeName "
$link | Should -Not -Exist
}
'an empty -Path' {
{ & $Command -Path '' -Target $target -ErrorAction Stop } |
Should -Throw -ErrorId " ParameterArgumentValidationError, $ TypeName "
Get-Content -LiteralPath $target | Should -Be 'EmptyGuardTarget'
}
}
}
}
Describe 'Get-NTFSHardLink errors, categories, output type, and positional binding' {
It 'Should accept piped strings by value, write errors with their categories for a folder and a missing path, and return the files in order' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'HardLinkPipelineFile'
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'HardLinkPipelineFolder' -Directory
$missing = Join-Path -Path $sandbox -ChildPath ( 'HardLinkPipelineMissing-{0}' -f [guid] :: NewGuid ( ) . ToString ( 'N' ) . Substring ( 0 , 8 ) )
$other = New-TestSandboxItem -Sandbox $sandbox -Name 'HardLinkPipelineOther'
$result = @ ( @ ( $file , $folder , $missing , $other ) | Get-NTFSHardLink -ErrorVariable linkErrors -ErrorAction SilentlyContinue )
$linkErrors | Should -HaveCount 2
$linkErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'GetHardLinkError,*'
$linkErrors [ 0 ] . CategoryInfo . Category | Should -Be 'InvalidArgument'
$linkErrors [ 0 ] . TargetObject | Should -Be $folder
$linkErrors [ 1 ] . FullyQualifiedErrorId | Should -BeLike 'FileNotFound,*'
$linkErrors [ 1 ] . CategoryInfo . Category | Should -Be 'ObjectNotFound'
$linkErrors [ 1 ] . TargetObject | Should -Be $missing
$result | Should -HaveCount 2
$result [ 0 ] | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
$result [ 0 ] . FullName | Should -Be $file
$result [ 1 ] | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
$result [ 1 ] . FullName | Should -Be $other
( Get-NTFSHardLink $file -ErrorAction Stop ) . FullName | Should -Be $file
}
}
Describe 'Pipeline continuation for the link-creating cmdlets' {
# Mirrors the PipelineControl.Tests.ps1 Untouched convention locally: a later command that ends the pipeline after
# the first pass-through object must leave the second link request completely untouched.
It 'New-NTFSHardLink should stop after the first pass-through object for Select-Object -First 1 and leave the second request untouched' {
$firstTarget = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinuationHardFirstTarget'
$secondTarget = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinuationHardSecondTarget'
$firstLink = Join-Path -Path $sandbox -ChildPath 'ContinuationHardFirstLink.txt'
$secondLink = Join-Path -Path $sandbox -ChildPath 'ContinuationHardSecondLink.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $firstLink , $secondLink
$requests = @ (
[pscustomobject] @ { Path = $firstLink ; Target = $firstTarget }
[pscustomobject] @ { Path = $secondLink ; Target = $secondTarget }
)
$result = @ ( $requests | New-NTFSHardLink -PassThru -ErrorVariable linkErrors -ErrorAction SilentlyContinue | Select-Object -First 1 )
$linkErrors | Should -BeNullOrEmpty
$result | Should -HaveCount 1
$firstLink | Should -Exist
$secondLink | Should -Not -Exist
}
It 'New-NTFSSymbolicLink should stop after the first pass-through object for Select-Object -First 1 and leave the second request untouched' -Skip : ( -not $canCreateSymbolicLinks ) {
$firstTarget = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinuationSymbolicFirstTarget'
$secondTarget = New-TestSandboxItem -Sandbox $sandbox -Name 'ContinuationSymbolicSecondTarget'
$firstLink = Join-Path -Path $sandbox -ChildPath 'ContinuationSymbolicFirstLink.txt'
$secondLink = Join-Path -Path $sandbox -ChildPath 'ContinuationSymbolicSecondLink.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $firstLink , $secondLink
$requests = @ (
[pscustomobject] @ { Path = $firstLink ; Target = $firstTarget }
[pscustomobject] @ { Path = $secondLink ; Target = $secondTarget }
)
$result = @ ( $requests | New-NTFSSymbolicLink -PassThru -ErrorVariable linkErrors -ErrorAction SilentlyContinue | Select-Object -First 1 )
$linkErrors | Should -BeNullOrEmpty
$result | Should -HaveCount 1
( Test-Path2 -Path $firstLink ) | Should -BeTrue
( Test-Path2 -Path $secondLink ) | Should -BeFalse
}
}
Describe 'New-NTFSSymbolicLink creates a link after an earlier failure in the same pipeline' {
# Every existing piped request fails (the suite runs without the privilege on most hosts), so a successful
# creation that follows a failed one in the same call was unverified; this is the weakest dimension the matrix
# found for this cmdlet (Continuation: Partly).
It 'Should write one ResourceExists error for an existing -Path and still create the links for the requests that follow it' -Skip : ( -not $canCreateSymbolicLinks ) {
$existingTarget = New-TestSandboxItem -Sandbox $sandbox -Name 'SuccessAfterFailureExistingTarget'
$existingPath = New-TestSandboxItem -Sandbox $sandbox -Name 'SuccessAfterFailureExistingPath'
Set-Content -LiteralPath $existingPath -Value 'ExistingPath'
$fileTarget = New-TestSandboxItem -Sandbox $sandbox -Name 'SuccessAfterFailureFileTarget'
Set-Content -LiteralPath $fileTarget -Value 'FileTargetData'
$fileLink = Join-Path -Path $sandbox -ChildPath 'SuccessAfterFailureFileLink.txt'
$folderTarget = New-TestSandboxItem -Sandbox $sandbox -Name 'SuccessAfterFailureFolderTarget' -Directory
$folderLink = Join-Path -Path $sandbox -ChildPath 'SuccessAfterFailureFolderLink'
Assert-TestSandboxPath -Sandbox $sandbox -Path $fileLink , $folderLink
$requests = @ (
[pscustomobject] @ { Path = $existingPath ; Target = $existingTarget }
[pscustomobject] @ { Path = $fileLink ; Target = $fileTarget }
[pscustomobject] @ { Path = $folderLink ; Target = $folderTarget }
)
$result = @ ( $requests | New-NTFSSymbolicLink -PassThru -ErrorVariable linkErrors -ErrorAction SilentlyContinue )
$linkErrors | Should -HaveCount 1
$linkErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'CreateSymbolicLinkError,*'
$linkErrors [ 0 ] . CategoryInfo . Category | Should -Be 'ResourceExists'
$linkErrors [ 0 ] . TargetObject | Should -Be $existingPath
Get-Content -LiteralPath $existingPath | Should -Be 'ExistingPath'
$result | Should -HaveCount 2
$result [ 0 ] | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
$result [ 0 ] . FullName | Should -Be $fileLink
$result [ 1 ] | Should -BeOfType [Alphaleonis.Win32.Filesystem.DirectoryInfo]
$result [ 1 ] . FullName | Should -Be $folderLink
( Get-Item -LiteralPath $fileLink -Force ) . LinkType | Should -Be 'SymbolicLink'
Get-Content -LiteralPath $fileLink | Should -Be 'FileTargetData'
}
}
Describe 'Enable-Privileges and Disable-Privileges error paths' {
# 'Enable Privilege Error' and 'Disable Privilege Error' run silently in many existing cleanups but are never
# asserted; both IDs contain spaces by design (BaseCmdlets.cs, OtherCmdlets.cs), which this pins as given.
BeforeAll {
$privateData = ( Get-Module -Name NTFSSecurity ) . PrivateData
$savedEnablePrivileges = $privateData [ 'EnablePrivileges' ]
# Runs in a child process: removing a privilege from a token is permanent for that process, so a token that
# holds all four (elevated) ends up holding at most three, and a token that holds none (basic user) stays at
# none either way. Either way, Enable-Privileges cannot enable all four and must report the error.
$enablePrivilegesErrorScript = Join-Path -Path $sandbox -ChildPath 'Invoke-EnablePrivilegesWithoutSecurity.ps1'
Assert-TestSandboxPath -Sandbox $sandbox -Path $enablePrivilegesErrorScript
Set-Content -LiteralPath $enablePrivilegesErrorScript -Value @ '
param ( $ModulePath )
Import-Module -Name $ModulePath -ErrorAction Stop
$process = [System.Diagnostics.Process] :: GetCurrentProcess ( )
try {
[ProcessPrivileges.ProcessExtensions] :: RemovePrivilege ( $process , [ProcessPrivileges.Privilege] :: Security ) | Out-Null
}
catch {
}
Enable-Privileges -ErrorVariable enableErrors -ErrorAction SilentlyContinue
'COUNT:{0}' -f @ ( $enableErrors ) . Count
if ( $enableErrors ) {
'ID:{0}' -f $enableErrors [ 0 ] . FullyQualifiedErrorId
'CATEGORY:{0}' -f $enableErrors [ 0 ] . CategoryInfo . Category
'EXCEPTIONTYPE:{0}' -f $enableErrors [ 0 ] . Exception . GetType ( ) . FullName
'MESSAGE:{0}' -f $enableErrors [ 0 ] . Exception . Message
}
' @
}
AfterAll {
$privateData [ 'EnablePrivileges' ] = $savedEnablePrivileges
}
AfterEach {
# A failing test here must not leave a privilege enabled for the tests that follow.
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
It 'Enable-Privileges should write Enable Privilege Error with SecurityError and AdjustPriviledgeException when it cannot enable all four privileges' {
$output = & ( Get-Process -Id $PID ) . Path -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $enablePrivilegesErrorScript -ModulePath ( [IO.Path] :: GetFullPath ( $modulePath ) )
( $output | Where-Object -FilterScript { $_ -like 'COUNT:*' } ) | Should -Be 'COUNT:1'
( $output | Where-Object -FilterScript { $_ -like 'ID:*' } ) | Should -Be 'ID:Enable Privilege Error,NTFSSecurity.EnablePrivileges'
( $output | Where-Object -FilterScript { $_ -like 'CATEGORY:*' } ) | Should -Be 'CATEGORY:SecurityError'
( $output | Where-Object -FilterScript { $_ -like 'EXCEPTIONTYPE:*' } ) | Should -Be 'EXCEPTIONTYPE:Security2.AdjustPriviledgeException'
( $output | Where-Object -FilterScript { $_ -like 'MESSAGE:*' } ) | Should -BeLike 'MESSAGE:*Could not enable requested privileges*'
}
It 'Disable-Privileges should write Disable Privilege Error with SecurityError and AdjustPriviledgeException and no output when nothing is enabled' {
# EnablePrivileges=$false stops BeginProcessing from auto-enabling the privileges first, which would otherwise
# always satisfy the "something is enabled" check and hide this error path.
$privateData [ 'EnablePrivileges' ] = $false
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
$result = @ ( Disable-Privileges -PassThru -ErrorVariable disableErrors -ErrorAction SilentlyContinue )
$disableErrors | Should -HaveCount 1
$disableErrors [ 0 ] . FullyQualifiedErrorId | Should -Be 'Disable Privilege Error,NTFSSecurity.DisablePrivileges'
$disableErrors [ 0 ] . CategoryInfo . Category | Should -Be 'SecurityError'
$disableErrors [ 0 ] . Exception | Should -BeOfType [Security2.AdjustPriviledgeException]
$disableErrors [ 0 ] . Exception . Message | Should -Be 'Privileges are not enabled'
$result | Should -BeNullOrEmpty
}
}
Describe 'Get-DiskSpace with several drive letters' {
# Reuses the free-letter lookup of the existing tests at ItemCmdlets.Tests.ps1:802 and :1063.
It 'Should return only the system drive and warn for a drive letter without a volume, regardless of order (<Order>)' -ForEach @ (
@ { Order = 'valid letter first' }
@ { Order = 'invalid letter first' }
) {
$used = @ ( ( Get-PSDrive -PSProvider FileSystem ) . Name ) + @ ( [System.IO.DriveInfo] :: GetDrives ( ) | ForEach-Object -Process { $_ . Name . Substring ( 0 , 1 ) } )
$letter = [char[]] ( 68 . . 90 ) | Where-Object -FilterScript { [string] $_ -notin $used } | Select-Object -First 1
if ( -not $letter ) {
Set-ItResult -Skipped -Because 'every drive letter is in use'
return
}
$missingLetter = " $ {letter}: "
$driveLetters = if ( $Order -eq 'valid letter first' ) { @ ( $env:SystemDrive , $missingLetter ) } else { @ ( $missingLetter , $env:SystemDrive ) }
$result = @ ( Get-DiskSpace -DriveLetter $driveLetters -WarningVariable spaceWarnings -WarningAction SilentlyContinue -ErrorVariable spaceErrors -ErrorAction SilentlyContinue )
$spaceErrors | Should -BeNullOrEmpty
$spaceWarnings . Message | Should -Be " Could not get drive details for ' $ missingLetter' "
$result | Should -HaveCount 1
$result [ 0 ] . DriveName | Should -Be ( '{0}\' -f $env:SystemDrive )
}
It 'Should accept a lower-case drive letter positionally and report the same total size as .NET' {
$systemDrive = New-Object -TypeName 'System.IO.DriveInfo' -ArgumentList $env:SystemDrive
$result = @ ( Get-DiskSpace $env:SystemDrive . ToLowerInvariant ( ) -ErrorAction Stop )
$result | Should -HaveCount 1
$result [ 0 ] . TotalNumberOfBytes | Should -Be $systemDrive . TotalSize
}
}
Describe 'Get-Privileges compared with an independent source' {
# whoami and the library name some privileges differently; these five are named consistently via 'Se<Name>Privilege'.
It 'Should match whoami /priv for the privileges that whoami and the library name the same way' {
$whoamiPrivileges = & whoami . exe / priv / fo csv | Select-Object -Skip 1 | ConvertFrom-Csv -Header 'Name' , 'Description' , 'State'
$libraryPrivileges = @ ( Get-Privileges -ErrorAction Stop )
foreach ( $name in 'ChangeNotify' , 'TakeOwnership' , 'Restore' , 'Backup' , 'Security' ) {
$whoamiName = 'Se{0}Privilege' -f $name
$whoamiEntry = @ ( $whoamiPrivileges | Where-Object -Property Name -EQ -Value $whoamiName )
$libraryEntry = @ ( $libraryPrivileges | Where-Object -Property Privilege -EQ -Value $name )
if ( $whoamiEntry . Count -gt 0 ) {
$libraryEntry | Should -HaveCount 1 -Because " Get-Privileges should list $ name when whoami lists $ whoamiName "
$libraryEntry [ 0 ] | Should -BeOfType [ProcessPrivileges.PrivilegeAndAttributes]
test(items,audit): resolve the findings of the independent review of the tests
Items: the comparison of Get-Privileges with whoami compares the State only
where whoami prints the English words, because the column is localized. The
rows 'as an array with an empty element' of Copy-Item2 and Move-Item2 now pass
an array: a switch unrolls @(''), so they had passed the plain empty string. The
empty-path rows of both cmdlets run in a folder of their own, so that a guard
that regressed would copy or move an empty folder and not the sandbox into
itself.
Audit: a piped AppliesTo property does not select PathSimple, the default set
PathComplex binds and the property is dropped (a descriptor object does select
SDSimple), so the rows named PathSimple bound PathComplex. The PathSimple rows
of Add-NTFSAudit and Remove-NTFSAudit now name -AppliesTo and pipe the rest,
and the Add rows use a folder and assert the scope of the new entry (None, not
the default ContainerInherit and ObjectInherit). The descriptor rows assert the
entry in the descriptor and, after Set-NTFSSecurityDescriptor, on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
# The State column is localized (a German Windows prints Aktiviert), so the states are compared only where
# whoami prints the English words; the privilege names above are not localized.
if ( $whoamiEntry [ 0 ] . State -in 'Enabled' , 'Disabled' ) {
$libraryEntry [ 0 ] . PrivilegeState . ToString ( ) | Should -Be $whoamiEntry [ 0 ] . State
$isEnabledByAttributes = ( $libraryEntry [ 0 ] . PrivilegeAttributes -band [ProcessPrivileges.PrivilegeAttributes] :: Enabled ) -eq [ProcessPrivileges.PrivilegeAttributes] :: Enabled
$isEnabledByAttributes | Should -Be ( $whoamiEntry [ 0 ] . State -eq 'Enabled' )
}
test(items): assert the cmdlets that the judged matrix found untested
Add Tests\ParameterSets.Items.Tests.ps1, closing TestNeeded gaps the judged
parameter-set matrix found for Copy-Item2, Get-ChildItem2, Get-Item2,
Move-Item2, Remove-Item2, Test-Path2, Get-FileHash2, Get-DiskSpace,
New-NTFSHardLink, Get-NTFSHardLink, New-NTFSSymbolicLink, Enable-Privileges,
Disable-Privileges, and Get-Privileges:
- Pipeline input by value for Copy-Item2, Move-Item2, Get-ChildItem2,
Get-Item2, and Remove-Item2 (never exercised before), including
continuation past a missing item and AlphaFS objects piped from
Get-ChildItem2.
- An empty string, $null, a piped empty string, or an empty array element
for -Path/-Target is rejected by ValidateNotNullOrEmpty and never falls
back to the current location, for Copy-Item2, Move-Item2, Remove-Item2,
New-NTFSHardLink, and New-NTFSSymbolicLink. Verified by running against
the built module in both PowerShell editions before being pinned.
- Get-NTFSHardLink error categories, output type, and positional binding.
- Pipeline continuation (Select-Object -First 1) for New-NTFSHardLink and
New-NTFSSymbolicLink, and a successful link after an earlier failure in
the same New-NTFSSymbolicLink call.
- Enable-Privileges and Disable-Privileges error paths (SecurityError,
Security2.AdjustPriviledgeException), runnable on any token.
- Get-DiskSpace with a valid and an invalid drive letter together in either
order, and positional/lower-case binding.
- Get-Privileges compared against whoami /priv for the five privileges
that both name the same way.
- Get-FileHash2 pipeline objects with -Algorithm, and a locked path before
a valid one with its error category and a relative-path hash.
Every It asserts persisted state, an ErrorId/category/target, continuation,
or an output type; none asserts only an English message. All four runtime
configurations (elevated/basic user x Windows PowerShell 5.1/PowerShell 7)
pass with 0 failures; two symbolic-link-privilege-gated tests skip on the
basic-user configurations and run on both elevated ones. Mutation testing
against 8 one-statement changes in a separate, discarded worktree confirmed
each targeted test fails for the right reason; production sources were
restored exactly after every mutation and no production code changed here.
Does not pin the Get-ChildItem2 -Filter dot rules, the Decision 22 link and
move/copy behaviors, the PrivilegeEnabler class, or the owner-restore
handlers. A probe of Get-NTFSHardLink and New-NTFSHardLink -PassThru
through a subst-mapped drive found a pre-existing defect (reported
separately, not fixed or pinned here): both combine the apparent drive
root with the volume-relative hard-link names from Windows, which throws
FileNotFoundException once the drive's root differs from the real NTFS
volume root.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
}
else {
$libraryEntry | Should -BeNullOrEmpty -Because " Get-Privileges should not list $ name when whoami does not list $ whoamiName "
}
}
}
}
Describe 'Get-FileHash2 with pipeline input and ordered failures' {
It 'Should accept Get-ChildItem2 objects piped by value, skip a folder, and match Get-FileHash for each file' {
$dir = New-TestSandboxItem -Sandbox $sandbox -Name 'HashPipelineDir' -Directory
$fileA = Join-Path -Path $dir -ChildPath 'A.txt'
$fileB = Join-Path -Path $dir -ChildPath 'B.txt'
$subFolder = Join-Path -Path $dir -ChildPath 'Sub'
Assert-TestSandboxPath -Sandbox $sandbox -Path $fileA , $fileB , $subFolder
Set-Content -LiteralPath $fileA -Value 'HashA'
Set-Content -LiteralPath $fileB -Value 'HashB'
New-Item -ItemType Directory -Path $subFolder | Out-Null
$result = @ ( Get-ChildItem2 -Path $dir -ErrorVariable childErrors -ErrorAction SilentlyContinue | Get-FileHash2 -ErrorVariable hashErrors -ErrorAction SilentlyContinue )
$childErrors | Should -BeNullOrEmpty
$hashErrors | Should -BeNullOrEmpty
$result | Should -HaveCount 2
@ ( $result . Name | Sort-Object ) | Should -Be @ ( 'A.txt' , 'B.txt' )
foreach ( $item in $result ) {
$sourcePath = Join-Path -Path $dir -ChildPath $item . Name
$item . Hash | Should -BeExactly ( Get-FileHash -LiteralPath $sourcePath -Algorithm SHA256 ) . Hash
$item . Algorithm | Should -Be 'SHA256'
$item . PSObject . TypeNames [ 0 ] | Should -Be 'Alphaleonis.Win32.Filesystem.FileInfo+Hash'
}
# The algorithm comes from a variable, like the data of FileHash.Tests.ps1: the test compares with Get-FileHash and uses no hash for security.
$legacyAlgorithm = 'MD5'
$md5Result = @ ( Get-ChildItem -LiteralPath $dir -File | Get-FileHash2 -Algorithm $legacyAlgorithm -ErrorAction Stop )
test(items): assert the cmdlets that the judged matrix found untested
Add Tests\ParameterSets.Items.Tests.ps1, closing TestNeeded gaps the judged
parameter-set matrix found for Copy-Item2, Get-ChildItem2, Get-Item2,
Move-Item2, Remove-Item2, Test-Path2, Get-FileHash2, Get-DiskSpace,
New-NTFSHardLink, Get-NTFSHardLink, New-NTFSSymbolicLink, Enable-Privileges,
Disable-Privileges, and Get-Privileges:
- Pipeline input by value for Copy-Item2, Move-Item2, Get-ChildItem2,
Get-Item2, and Remove-Item2 (never exercised before), including
continuation past a missing item and AlphaFS objects piped from
Get-ChildItem2.
- An empty string, $null, a piped empty string, or an empty array element
for -Path/-Target is rejected by ValidateNotNullOrEmpty and never falls
back to the current location, for Copy-Item2, Move-Item2, Remove-Item2,
New-NTFSHardLink, and New-NTFSSymbolicLink. Verified by running against
the built module in both PowerShell editions before being pinned.
- Get-NTFSHardLink error categories, output type, and positional binding.
- Pipeline continuation (Select-Object -First 1) for New-NTFSHardLink and
New-NTFSSymbolicLink, and a successful link after an earlier failure in
the same New-NTFSSymbolicLink call.
- Enable-Privileges and Disable-Privileges error paths (SecurityError,
Security2.AdjustPriviledgeException), runnable on any token.
- Get-DiskSpace with a valid and an invalid drive letter together in either
order, and positional/lower-case binding.
- Get-Privileges compared against whoami /priv for the five privileges
that both name the same way.
- Get-FileHash2 pipeline objects with -Algorithm, and a locked path before
a valid one with its error category and a relative-path hash.
Every It asserts persisted state, an ErrorId/category/target, continuation,
or an output type; none asserts only an English message. All four runtime
configurations (elevated/basic user x Windows PowerShell 5.1/PowerShell 7)
pass with 0 failures; two symbolic-link-privilege-gated tests skip on the
basic-user configurations and run on both elevated ones. Mutation testing
against 8 one-statement changes in a separate, discarded worktree confirmed
each targeted test fails for the right reason; production sources were
restored exactly after every mutation and no production code changed here.
Does not pin the Get-ChildItem2 -Filter dot rules, the Decision 22 link and
move/copy behaviors, the PrivilegeEnabler class, or the owner-restore
handlers. A probe of Get-NTFSHardLink and New-NTFSHardLink -PassThru
through a subst-mapped drive found a pre-existing defect (reported
separately, not fixed or pinned here): both combine the apparent drive
root with the volume-relative hard-link names from Windows, which throws
FileNotFoundException once the drive's root differs from the real NTFS
volume root.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
$md5Result | Should -HaveCount 2
foreach ( $item in $md5Result ) {
$sourcePath = Join-Path -Path $dir -ChildPath $item . Name
$item . Hash | Should -BeExactly ( Get-FileHash -LiteralPath $sourcePath -Algorithm $legacyAlgorithm ) . Hash
test(items): assert the cmdlets that the judged matrix found untested
Add Tests\ParameterSets.Items.Tests.ps1, closing TestNeeded gaps the judged
parameter-set matrix found for Copy-Item2, Get-ChildItem2, Get-Item2,
Move-Item2, Remove-Item2, Test-Path2, Get-FileHash2, Get-DiskSpace,
New-NTFSHardLink, Get-NTFSHardLink, New-NTFSSymbolicLink, Enable-Privileges,
Disable-Privileges, and Get-Privileges:
- Pipeline input by value for Copy-Item2, Move-Item2, Get-ChildItem2,
Get-Item2, and Remove-Item2 (never exercised before), including
continuation past a missing item and AlphaFS objects piped from
Get-ChildItem2.
- An empty string, $null, a piped empty string, or an empty array element
for -Path/-Target is rejected by ValidateNotNullOrEmpty and never falls
back to the current location, for Copy-Item2, Move-Item2, Remove-Item2,
New-NTFSHardLink, and New-NTFSSymbolicLink. Verified by running against
the built module in both PowerShell editions before being pinned.
- Get-NTFSHardLink error categories, output type, and positional binding.
- Pipeline continuation (Select-Object -First 1) for New-NTFSHardLink and
New-NTFSSymbolicLink, and a successful link after an earlier failure in
the same New-NTFSSymbolicLink call.
- Enable-Privileges and Disable-Privileges error paths (SecurityError,
Security2.AdjustPriviledgeException), runnable on any token.
- Get-DiskSpace with a valid and an invalid drive letter together in either
order, and positional/lower-case binding.
- Get-Privileges compared against whoami /priv for the five privileges
that both name the same way.
- Get-FileHash2 pipeline objects with -Algorithm, and a locked path before
a valid one with its error category and a relative-path hash.
Every It asserts persisted state, an ErrorId/category/target, continuation,
or an output type; none asserts only an English message. All four runtime
configurations (elevated/basic user x Windows PowerShell 5.1/PowerShell 7)
pass with 0 failures; two symbolic-link-privilege-gated tests skip on the
basic-user configurations and run on both elevated ones. Mutation testing
against 8 one-statement changes in a separate, discarded worktree confirmed
each targeted test fails for the right reason; production sources were
restored exactly after every mutation and no production code changed here.
Does not pin the Get-ChildItem2 -Filter dot rules, the Decision 22 link and
move/copy behaviors, the PrivilegeEnabler class, or the owner-restore
handlers. A probe of Get-NTFSHardLink and New-NTFSHardLink -PassThru
through a subst-mapped drive found a pre-existing defect (reported
separately, not fixed or pinned here): both combine the apparent drive
root with the volume-relative hard-link names from Windows, which throws
FileNotFoundException once the drive's root differs from the real NTFS
volume root.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
}
}
It 'Should write GetHashError with its category for a locked file first, hash the file that follows it, and resolve a relative path' {
$locked = New-TestSandboxItem -Sandbox $sandbox -Name 'HashLocked'
$first = New-TestSandboxItem -Sandbox $sandbox -Name 'HashFirstFollowing'
Set-Content -LiteralPath $first -Value 'FollowingContent'
$stream = [IO.File] :: Open ( $locked , [IO.FileMode] :: Open , [IO.FileAccess] :: Read , [IO.FileShare] :: None )
try {
$result = @ ( Get-FileHash2 -Path $locked , $first -ErrorVariable hashErrors -ErrorAction SilentlyContinue )
}
finally {
$stream . Dispose ( )
}
$hashErrors | Should -HaveCount 1
$hashErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'GetHashError,*'
$hashErrors [ 0 ] . CategoryInfo . Category | Should -Be 'WriteError'
$hashErrors [ 0 ] . TargetObject | Should -Be $locked
$result | Should -HaveCount 1
$result [ 0 ] . Name | Should -BeExactly ( Split-Path -Path $first -Leaf )
$result [ 0 ] . Hash | Should -BeExactly ( Get-FileHash -LiteralPath $first -Algorithm SHA256 ) . Hash
Push-Location -LiteralPath ( Split-Path -Path $first -Parent )
try {
$relativeResult = Get-FileHash2 -Path ( Split-Path -Path $first -Leaf ) -ErrorAction Stop
}
finally {
Pop-Location
}
$relativeResult . Hash | Should -BeExactly $result [ 0 ] . Hash
}
}