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'
test(items): close budget gaps for item, link, disk-space, and privilege cmdlets
Adds pipeline/positional binding, long-path, bracket/wildcard literal-path,
drive-root, omitted-path-default, and error category/target coverage for
Copy-Item2, Get-ChildItem2, Get-Item2, Move-Item2, Remove-Item2, Test-Path2,
Get-FileHash2, and Get-DiskSpace; positional binding, FileInfo pipeline input,
and share-error category coverage for New-NTFSHardLink and Get-NTFSHardLink;
positional binding and folder LinkType/target coverage for
New-NTFSSymbolicLink; and -PassThru privilege-state coverage for
Enable-Privileges and Disable-Privileges. No product code changed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
# The restricted token of the basic-user runner cannot map a drive letter or reach the administrative share.
$canMapDrive = Test-DriveMappingAvailable
$canUseAdminShare = Test-AdminShareAvailable
$isElevated = Test-IsElevated
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
}
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
}
}
test(items): close budget gaps for item, link, disk-space, and privilege cmdlets
Adds pipeline/positional binding, long-path, bracket/wildcard literal-path,
drive-root, omitted-path-default, and error category/target coverage for
Copy-Item2, Get-ChildItem2, Get-Item2, Move-Item2, Remove-Item2, Test-Path2,
Get-FileHash2, and Get-DiskSpace; positional binding, FileInfo pipeline input,
and share-error category coverage for New-NTFSHardLink and Get-NTFSHardLink;
positional binding and folder LinkType/target coverage for
New-NTFSSymbolicLink; and -PassThru privilege-state coverage for
Enable-Privileges and Disable-Privileges. No product code changed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
Describe 'Bracket and wildcard characters in a path are treated literally' {
# A bracket is an ordinary character in a path, as the existing -Filter test already pins for Get-ChildItem2
# (ItemCmdlets.Tests.ps1:202); this closes the same gap for the path parameter of the other item cmdlets.
It '<Command> should treat a path with brackets in the name as a literal file name' -ForEach @ (
@ { Command = 'Copy-Item2' }
@ { Command = 'Move-Item2' }
@ { Command = 'Get-Item2' }
@ { Command = 'Remove-Item2' }
) {
$sourceFolder = New-TestSandboxItem -Sandbox $sandbox -Name ( 'Bracket{0}Source' -f $Command . Replace ( '-' , '' ) ) -Directory
$bracketFile = Join-Path -Path $sourceFolder -ChildPath 'Report[1].txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $bracketFile
Set-Content -LiteralPath $bracketFile -Value 'Bracket'
switch ( $Command ) {
'Copy-Item2' {
$destination = Join-Path -Path $sourceFolder -ChildPath 'Copied'
New-Item -ItemType Directory -Path $destination | Out-Null
$result = Copy-Item2 -Path $bracketFile -Destination $destination -PassThru $true -ErrorAction Stop
$result . Name | Should -Be 'Report[1].txt'
Get-Content -LiteralPath ( Join-Path -Path $destination -ChildPath 'Report[1].txt' ) | Should -Be 'Bracket'
# Should -Exist uses a non-literal Test-Path, which reads the brackets as a character class and
# would misreport a file that is actually there; [IO.File]::Exists is a literal, independent oracle.
[IO.File] :: Exists ( $bracketFile ) | Should -BeTrue
}
'Move-Item2' {
$destination = Join-Path -Path $sourceFolder -ChildPath 'Moved'
New-Item -ItemType Directory -Path $destination | Out-Null
$result = Move-Item2 -Path $bracketFile -Destination $destination -PassThru $true -ErrorAction Stop
$result . Name | Should -Be 'Report[1].txt'
[IO.File] :: Exists ( $bracketFile ) | Should -BeFalse
Get-Content -LiteralPath ( Join-Path -Path $destination -ChildPath 'Report[1].txt' ) | Should -Be 'Bracket'
}
'Get-Item2' {
$result = Get-Item2 -Path $bracketFile -ErrorAction Stop
$result . Name | Should -Be 'Report[1].txt'
$result | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
}
'Remove-Item2' {
$result = Remove-Item2 -Path $bracketFile -PassThru -ErrorAction Stop
$result . Name | Should -Be 'Report[1].txt'
[IO.File] :: Exists ( $bracketFile ) | Should -BeFalse
}
}
}
# '*' and '?' are not in [IO.Path]::GetInvalidPathChars() in either edition (only '|' and the control characters
# are, and Windows PowerShell additionally rejects '"', '<', '>'), so a literal asterisk or question mark reaches
# the file system as an ordinary character instead of raising ArgumentException: AlphaFS, like System.IO, looks
# for the exact literal name and reports it missing rather than expanding the character as a search pattern.
It '<Command> should not expand an asterisk in a path as a wildcard and should report FileNotFound' -ForEach @ (
@ { Command = 'Move-Item2' ; ErrorId = 'FileNotFound' }
@ { Command = 'Remove-Item2' ; ErrorId = 'FileNotFound' }
) {
$sourceFolder = New-TestSandboxItem -Sandbox $sandbox -Name ( 'Wildcard{0}Source' -f $Command . Replace ( '-' , '' ) ) -Directory
$fileA = Join-Path -Path $sourceFolder -ChildPath 'FileA.txt'
$fileB = Join-Path -Path $sourceFolder -ChildPath 'FileB.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $fileA , $fileB
Set-Content -LiteralPath $fileA -Value 'A'
Set-Content -LiteralPath $fileB -Value 'B'
$wildcardPath = Join-Path -Path $sourceFolder -ChildPath 'File*.txt'
switch ( $Command ) {
'Move-Item2' {
$destination = Join-Path -Path $sourceFolder -ChildPath 'Destination'
New-Item -ItemType Directory -Path $destination | Out-Null
Move-Item2 -Path $wildcardPath -Destination $destination -ErrorVariable itemErrors -ErrorAction SilentlyContinue
}
'Remove-Item2' {
Remove-Item2 -Path $wildcardPath -ErrorVariable itemErrors -ErrorAction SilentlyContinue
}
}
$itemErrors | Should -HaveCount 1
$itemErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike " $ ErrorId,* "
$itemErrors [ 0 ] . CategoryInfo . Category | Should -Be 'ObjectNotFound'
$itemErrors [ 0 ] . TargetObject | Should -Be $wildcardPath
$fileA | Should -Exist
$fileB | Should -Exist
Get-Content -LiteralPath $fileA | Should -Be 'A'
Get-Content -LiteralPath $fileB | Should -Be 'B'
}
}
Describe 'Items whose path is longer than 260 characters' {
BeforeAll {
Add-Type -Path ( Join-Path -Path ( Get-Module -Name NTFSSecurity ) . ModuleBase -ChildPath 'AlphaFS.dll' )
# Builds a fresh folder tree and file below it whose full path is longer than 260 characters, in its own
# branch of the sandbox, and returns the file's full path. Mirrors ItemCmdlets.Tests.ps1:1029: only the short
# root goes through Assert-TestSandboxPath, because [IO.Path] (which it calls) throws PathTooLongException
# for the long path itself in Windows PowerShell; AlphaFS does not have that limit. A script block, not a
# function: PowerShell does not parse an attribute on a function statement nested inside another block, so
# PSUseShouldProcessForStateChangingFunctions (its name starts with New) could not be suppressed that way.
$newTestLongPathFile = {
param ( [ Parameter ( Mandatory ) ] [string] $Name )
$root = New-TestSandboxItem -Sandbox $sandbox -Name $Name -Directory
$deep = Join-Path -Path $root -ChildPath ( ( ( 'A' * 100 ) , ( 'B' * 100 ) , ( 'C' * 100 ) ) -join '\' )
[Alphaleonis.Win32.Filesystem.Directory] :: CreateDirectory ( $deep ) | Out-Null
$file = Join-Path -Path $deep -ChildPath 'Long.txt'
[Alphaleonis.Win32.Filesystem.File] :: WriteAllText ( $file , 'LongPathContent' )
if ( $file . Length -le 260 ) {
throw " The test path ' $ file' is not longer than 260 characters. "
}
$file
}
}
It 'Get-Item2 should return the file and its folder' {
$file = & $newTestLongPathFile -Name 'GetItemLong'
$fileResult = Get-Item2 -Path $file -ErrorAction Stop
$folderResult = Get-Item2 -Path ( Split-Path -Path $file -Parent ) -ErrorAction Stop
$fileResult | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
$fileResult . FullName | Should -Be $file
$folderResult | Should -BeOfType [Alphaleonis.Win32.Filesystem.DirectoryInfo]
}
It 'Get-ChildItem2 should list the content of the folder' {
$file = & $newTestLongPathFile -Name 'ChildItemLong'
$result = @ ( Get-ChildItem2 -Path ( Split-Path -Path $file -Parent ) -ErrorAction Stop )
$result | Should -HaveCount 1
$result [ 0 ] . FullName | Should -Be $file
}
It 'Get-FileHash2 should hash the file and match an independent SHA256 read through AlphaFS' {
$file = & $newTestLongPathFile -Name 'HashLong'
$bytes = [Alphaleonis.Win32.Filesystem.File] :: ReadAllBytes ( $file )
$expectedHash = -join ( ( [Security.Cryptography.SHA256] :: Create ( ) . ComputeHash ( $bytes ) ) | ForEach-Object -Process { $_ . ToString ( 'X2' ) } )
$result = Get-FileHash2 -Path $file -ErrorAction Stop
$result . Hash | Should -BeExactly $expectedHash
}
It 'Copy-Item2 should copy the file to a short destination and leave the source in place' {
$file = & $newTestLongPathFile -Name 'CopyLong'
$destination = New-TestSandboxItem -Sandbox $sandbox -Name 'CopyLongDestination' -Directory
$result = Copy-Item2 -Path $file -Destination $destination -PassThru $true -ErrorAction Stop
$result | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
$copied = Join-Path -Path $destination -ChildPath 'Long.txt'
Get-Content -LiteralPath $copied | Should -Be 'LongPathContent'
[Alphaleonis.Win32.Filesystem.File] :: Exists ( $file ) | Should -BeTrue
}
It 'Move-Item2 should move the file to a short destination and remove the source' {
$file = & $newTestLongPathFile -Name 'MoveLong'
$destination = New-TestSandboxItem -Sandbox $sandbox -Name 'MoveLongDestination' -Directory
$result = Move-Item2 -Path $file -Destination $destination -PassThru $true -ErrorAction Stop
$result | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
[Alphaleonis.Win32.Filesystem.File] :: Exists ( $file ) | Should -BeFalse
$moved = Join-Path -Path $destination -ChildPath 'Long.txt'
Get-Content -LiteralPath $moved | Should -Be 'LongPathContent'
}
}
Describe 'Item cmdlets at the root of a mapped drive' -Skip : ( -not $canMapDrive ) {
# NEVER the real root of a volume: a drive letter that subst maps to a sandbox folder is the root of a drive for
# Windows and for the module (see Tests\DriveRoot.Tests.ps1), without touching a real volume.
BeforeAll {
$driveSandbox = New-TestSandbox -Name 'ItemsDriveRoot'
$mapped = New-TestSandboxItem -Sandbox $driveSandbox -Name 'Mapped' -Directory
$driveRoot = New-TestDriveMapping -Sandbox $driveSandbox -Path $mapped
if ( -not $driveRoot ) {
throw 'No drive letter could be mapped to the sandbox folder.'
}
$existingFile = Join-Path -Path $mapped -ChildPath 'Existing.txt'
Assert-TestSandboxPath -Sandbox $driveSandbox -Path $existingFile
Set-Content -LiteralPath $existingFile -Value 'Existing'
}
AfterAll {
if ( $driveRoot ) {
Remove-TestDriveMapping -Root $driveRoot
}
Remove-TestSandbox -Sandbox $driveSandbox
}
It 'Get-Item2 should return a DirectoryInfo object for the root of the mapped drive' {
$result = Get-Item2 -Path $driveRoot -ErrorAction Stop
$result | Should -BeOfType [Alphaleonis.Win32.Filesystem.DirectoryInfo]
$result . FullName . TrimEnd ( '\' ) | Should -Be $driveRoot . TrimEnd ( '\' )
}
It 'Get-ChildItem2 should list the content at the root of the mapped drive' {
$result = @ ( Get-ChildItem2 -Path $driveRoot -ErrorAction Stop )
$result . Name | Should -Contain 'Existing.txt'
}
It 'Test-Path2 should find the root of the mapped drive as a container' {
Test-Path2 -Path $driveRoot -PathType Container -ErrorAction Stop | Should -BeTrue
}
It 'Copy-Item2 should copy a file to the root of the mapped drive' {
$source = New-TestSandboxItem -Sandbox $driveSandbox -Name 'CopyToDriveRootSource'
Set-Content -LiteralPath $source -Value 'CopyToRoot'
$result = Copy-Item2 -Path $source -Destination $driveRoot -PassThru $true -ErrorAction Stop
$result | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
$copied = Join-Path -Path $mapped -ChildPath ( Split-Path -Path $source -Leaf )
Get-Content -LiteralPath $copied | Should -Be 'CopyToRoot'
$source | Should -Exist
}
It 'Move-Item2 should move a file to the root of the mapped drive' {
$source = New-TestSandboxItem -Sandbox $driveSandbox -Name 'MoveToDriveRootSource'
Set-Content -LiteralPath $source -Value 'MoveToRoot'
$result = Move-Item2 -Path $source -Destination $driveRoot -PassThru $true -ErrorAction Stop
$result | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
$source | Should -Not -Exist
$moved = Join-Path -Path $mapped -ChildPath ( Split-Path -Path $source -Leaf )
Get-Content -LiteralPath $moved | Should -Be 'MoveToRoot'
}
}
Describe 'Omitted -Path falls back to the current location' {
BeforeAll {
$currentLocationFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'OmittedPathLocation' -Directory
$currentLocationFile = Join-Path -Path $currentLocationFolder -ChildPath 'Child.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $currentLocationFile
Set-Content -LiteralPath $currentLocationFile -Value 'Child'
}
It 'Get-Item2 should return the folder of the current location' {
Push-Location -LiteralPath $currentLocationFolder
try {
$result = Get-Item2 -ErrorAction Stop
}
finally {
Pop-Location
}
$result | Should -BeOfType [Alphaleonis.Win32.Filesystem.DirectoryInfo]
$result . FullName . TrimEnd ( '\' ) | Should -Be $currentLocationFolder . TrimEnd ( '\' )
}
It 'Get-ChildItem2 should list the content of the current location' {
Push-Location -LiteralPath $currentLocationFolder
try {
$result = @ ( Get-ChildItem2 -ErrorAction Stop )
}
finally {
Pop-Location
}
$result | Should -HaveCount 1
$result [ 0 ] . FullName | Should -Be $currentLocationFile
}
}
Describe 'Get-ChildItem2 parameter interactions' {
BeforeAll {
$interactionFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'ParamInteractions' -Directory
$interactionSubFolder = Join-Path -Path $interactionFolder -ChildPath 'Sub'
$interactionTopFile = Join-Path -Path $interactionFolder -ChildPath 'Top.txt'
$interactionNestedFile = Join-Path -Path $interactionSubFolder -ChildPath 'Nested.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $interactionSubFolder , $interactionTopFile , $interactionNestedFile
New-Item -ItemType Directory -Path $interactionSubFolder | Out-Null
Set-Content -LiteralPath $interactionTopFile -Value 'Top'
Set-Content -LiteralPath $interactionNestedFile -Value 'Nested'
$attributeFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'AttributesIgnored' -Directory
$hiddenSystemFile = Join-Path -Path $attributeFolder -ChildPath 'HiddenSystem.txt'
$systemFile = Join-Path -Path $attributeFolder -ChildPath 'System.txt'
$hiddenFile = Join-Path -Path $attributeFolder -ChildPath 'Hidden.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $hiddenSystemFile , $systemFile , $hiddenFile
Set-Content -LiteralPath $hiddenSystemFile -Value 'HS'
Set-Content -LiteralPath $systemFile -Value 'S'
Set-Content -LiteralPath $hiddenFile -Value 'H'
[IO.File] :: SetAttributes ( $hiddenSystemFile , [IO.FileAttributes] 'Hidden, System' )
[IO.File] :: SetAttributes ( $systemFile , [IO.FileAttributes] 'System' )
[IO.File] :: SetAttributes ( $hiddenFile , [IO.FileAttributes] 'Hidden' )
}
It 'Should resolve a relative -Path against the current location' {
Push-Location -LiteralPath $interactionFolder
try {
$result = @ ( Get-ChildItem2 -Path ( Split-Path -Path $interactionSubFolder -Leaf ) -ErrorAction Stop )
}
finally {
Pop-Location
}
$result | Should -HaveCount 1
$result [ 0 ] . FullName | Should -Be $interactionNestedFile
}
It 'Should return only the folder when -Directory and -File are given together' {
$result = @ ( Get-ChildItem2 -Path $interactionFolder -Directory -File -ErrorAction Stop )
$result | Should -HaveCount 1
$result [ 0 ] | Should -BeOfType [Alphaleonis.Win32.Filesystem.DirectoryInfo]
$result [ 0 ] . FullName | Should -Be $interactionSubFolder
}
# The cmdlet page documents that -Attributes makes the cmdlet ignore -Force, -Hidden, -System, and -ReadOnly, and
# that it returns a matching hidden item without -Force; each row returns the same names as -Attributes alone,
# which these switches would narrow or widen if they were not ignored.
It 'Should return the same matches for -Attributes <AttributesValue> regardless of -Force, -Hidden, -System, and -ReadOnly (<Case>)' -ForEach @ (
@ { Case = 'alone' ; AttributesValue = 'System' ; Switches = @ { } }
@ { Case = 'with every other switch set' ; AttributesValue = 'System' ; Switches = @ { Force = $true ; Hidden = $true ; System = $true ; ReadOnly = $true } }
@ { Case = 'alone, matching a hidden item without -Force' ; AttributesValue = 'Hidden' ; Switches = @ { } }
@ { Case = 'with -ReadOnly set' ; AttributesValue = 'Hidden' ; Switches = @ { ReadOnly = $true } }
) {
$expected = if ( $AttributesValue -eq 'System' ) { @ ( 'HiddenSystem.txt' , 'System.txt' ) } else { @ ( 'Hidden.txt' , 'HiddenSystem.txt' ) }
$result = @ ( Get-ChildItem2 -Path $attributeFolder -Attributes $AttributesValue @Switches -ErrorAction Stop )
@ ( $result . Name | Sort-Object ) | Should -Be $expected
}
It 'Should have no effect without -Recurse' {
$withDepth = @ ( Get-ChildItem2 -Path $interactionFolder -Depth 0 -ErrorAction Stop )
$withoutDepth = @ ( Get-ChildItem2 -Path $interactionFolder -ErrorAction Stop )
@ ( $withDepth . Name | Sort-Object ) | Should -Be @ ( 'Sub' , 'Top.txt' )
@ ( $withDepth . Name | Sort-Object ) | Should -Be @ ( $withoutDepth . Name | Sort-Object )
}
It 'Should list the same items through the dir2 alias' {
$result = @ ( dir2 -Path $interactionFolder -ErrorAction Stop )
@ ( $result . Name | Sort-Object ) | Should -Be @ ( 'Sub' , 'Top.txt' )
}
It 'Should write AttributesEmpty with its category and target for an empty -Attributes value' {
$caught = $null
try {
Get-ChildItem2 -Path $interactionFolder -Attributes 0 -ErrorAction Stop
}
catch {
$caught = $_
}
$caught . FullyQualifiedErrorId | Should -Be 'AttributesEmpty,NTFSSecurity.GetChildItem2'
$caught . CategoryInfo . Category | Should -Be 'InvalidArgument'
$caught . TargetObject | Should -Be ( [IO.FileAttributes] 0 )
}
}
Describe 'Copy-Item2 destination conflicts and output' {
It 'Should replace an existing file directly with -Force, with PassThru=<_>' -ForEach @ ( $false , $true ) {
$sourceFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'CopyForceSource' -Directory
$destinationFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'CopyForceDestination' -Directory
$source = Join-Path -Path $sourceFolder -ChildPath 'Replace.txt'
$target = Join-Path -Path $destinationFolder -ChildPath 'Replace.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $source , $target
Set-Content -LiteralPath $source -Value 'NewContent'
Set-Content -LiteralPath $target -Value 'OldContent'
$result = @ ( Copy-Item2 -Path $source -Destination $destinationFolder -Force -PassThru $_ -ErrorAction Stop )
Get-Content -LiteralPath $target | Should -Be 'NewContent'
$source | Should -Exist
if ( $_ ) {
$result | Should -HaveCount 1
$result [ 0 ] . FullName | Should -Be $target
}
else {
$result | Should -BeNullOrEmpty
}
}
# The folder case of the same conflict (ItemCmdlets.Tests.ps1:633) already asserts ID, category, and target; this
# file case was asserted by error count only.
It 'Should write DestinationFileAlreadyExists with its category and target for a file that exists at the destination' {
$source = New-TestSandboxItem -Sandbox $sandbox -Name 'FileConflictSource'
$destinationFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'FileConflictDestination' -Directory
$existing = Join-Path -Path $destinationFolder -ChildPath ( Split-Path -Path $source -Leaf )
Assert-TestSandboxPath -Sandbox $sandbox -Path $existing
Set-Content -LiteralPath $existing -Value 'Existing'
Copy-Item2 -Path $source -Destination $destinationFolder -ErrorVariable itemErrors -ErrorAction SilentlyContinue
$itemErrors | Should -HaveCount 1
$itemErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'DestinationFileAlreadyExists,*'
$itemErrors [ 0 ] . CategoryInfo . Category | Should -Be 'ResourceExists'
$itemErrors [ 0 ] . TargetObject | Should -Be $existing
Get-Content -LiteralPath $existing | Should -Be 'Existing'
}
It '-PassThru should return the output type for a file and for a folder, and write nothing without -PassThru' {
$sourceFile = New-TestSandboxItem -Sandbox $sandbox -Name 'PassThruTypeFile'
$sourceFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'PassThruTypeFolder' -Directory
$destination = New-TestSandboxItem -Sandbox $sandbox -Name 'PassThruTypeDestination' -Directory
$fileResult = @ ( Copy-Item2 -Path $sourceFile -Destination $destination -PassThru $true -ErrorAction Stop )
$folderResult = @ ( Copy-Item2 -Path $sourceFolder -Destination ( Join-Path -Path $destination -ChildPath 'CopiedFolder' ) -PassThru $true -ErrorAction Stop )
$quietResult = @ ( Copy-Item2 -Path $sourceFile -Destination ( Join-Path -Path $destination -ChildPath 'Quiet' ) -ErrorAction Stop )
$fileResult | Should -HaveCount 1
$fileResult [ 0 ] | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
$folderResult | Should -HaveCount 1
$folderResult [ 0 ] | Should -BeOfType [Alphaleonis.Win32.Filesystem.DirectoryInfo]
$quietResult | Should -BeNullOrEmpty
}
}
Describe 'Move-Item2 folder trees, in-place renames, and -Force' {
It 'Should move a non-empty folder tree to a new destination' {
$sourceFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'MoveTreeSource' -Directory
$nestedFolder = Join-Path -Path $sourceFolder -ChildPath 'Nested'
$topFile = Join-Path -Path $sourceFolder -ChildPath 'Top.txt'
$nestedFile = Join-Path -Path $nestedFolder -ChildPath 'Inner.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $nestedFolder , $topFile , $nestedFile
New-Item -ItemType Directory -Path $nestedFolder | Out-Null
Set-Content -LiteralPath $topFile -Value 'Top'
Set-Content -LiteralPath $nestedFile -Value 'Inner'
$destinationParent = New-TestSandboxItem -Sandbox $sandbox -Name 'MoveTreeDestinationParent' -Directory
$result = Move-Item2 -Path $sourceFolder -Destination $destinationParent -PassThru $true -ErrorAction Stop
$movedFolder = Join-Path -Path $destinationParent -ChildPath ( Split-Path -Path $sourceFolder -Leaf )
$result | Should -BeOfType [Alphaleonis.Win32.Filesystem.DirectoryInfo]
$sourceFolder | Should -Not -Exist
Get-Content -LiteralPath ( Join-Path -Path $movedFolder -ChildPath 'Top.txt' ) | Should -Be 'Top'
Get-Content -LiteralPath ( Join-Path -Path $movedFolder -ChildPath 'Nested\Inner.txt' ) | Should -Be 'Inner'
}
It 'Should rename an item in place on the same volume' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'RenameSource'
Set-Content -LiteralPath $file -Value 'RenameContent'
$renamed = Join-Path -Path ( Split-Path -Path $file -Parent ) -ChildPath 'RenameTarget.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $renamed
$result = Move-Item2 -Path $file -Destination $renamed -PassThru $true -ErrorAction Stop
$file | Should -Not -Exist
$renamed | Should -Exist
Get-Content -LiteralPath $renamed | Should -Be 'RenameContent'
$result . FullName | Should -Be $renamed
}
# Unlike Copy-Item2 -Force, which merges into an existing folder of the same name (ItemCmdlets.Tests.ps1:681),
# the cmdlet page documents that Move-Item2 -Force "overwrites an existing destination item" and runs with the
# ReplaceExisting option (Move-Item2.md, -Force and the Notes section); a non-empty folder at the destination
# confirms that this means a full replace, not a merge, which a probe showed first (the existing folder and the
# file it held were gone afterwards, and the moved folder took its place with no error).
test(items): close budget gaps for item, link, disk-space, and privilege cmdlets
Adds pipeline/positional binding, long-path, bracket/wildcard literal-path,
drive-root, omitted-path-default, and error category/target coverage for
Copy-Item2, Get-ChildItem2, Get-Item2, Move-Item2, Remove-Item2, Test-Path2,
Get-FileHash2, and Get-DiskSpace; positional binding, FileInfo pipeline input,
and share-error category coverage for New-NTFSHardLink and Get-NTFSHardLink;
positional binding and folder LinkType/target coverage for
New-NTFSSymbolicLink; and -PassThru privilege-state coverage for
Enable-Privileges and Disable-Privileges. No product code changed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
It 'Should replace an existing folder outright with -Force, not merge it, and write no error' {
$sourceFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'MoveForceFolderSource' -Directory
$destinationParent = New-TestSandboxItem -Sandbox $sandbox -Name 'MoveForceFolderDestinationParent' -Directory
$existingFolder = Join-Path -Path $destinationParent -ChildPath ( Split-Path -Path $sourceFolder -Leaf )
Assert-TestSandboxPath -Sandbox $sandbox -Path $existingFolder
New-Item -ItemType Directory -Path $existingFolder | Out-Null
$sourceContent = Join-Path -Path $sourceFolder -ChildPath 'FromSource.txt'
$existingContent = Join-Path -Path $existingFolder -ChildPath 'AlreadyThere.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $sourceContent , $existingContent
Set-Content -LiteralPath $sourceContent -Value 'FromSource'
Set-Content -LiteralPath $existingContent -Value 'AlreadyThere'
$result = Move-Item2 -Path $sourceFolder -Destination $destinationParent -Force -PassThru $true -ErrorVariable itemErrors -ErrorAction SilentlyContinue
$itemErrors | Should -BeNullOrEmpty
$result | Should -BeOfType [Alphaleonis.Win32.Filesystem.DirectoryInfo]
$result . FullName | Should -Be $existingFolder
[IO.Directory] :: Exists ( $sourceFolder ) | Should -BeFalse
Get-Content -LiteralPath ( Join-Path -Path $existingFolder -ChildPath 'FromSource.txt' ) | Should -Be 'FromSource'
[IO.File] :: Exists ( $existingContent ) | Should -BeFalse
}
}
Describe 'Remove-Item2 defaults, aliases, and error details' {
It 'Should do nothing when -Path is omitted' {
$omittedPathFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveOmittedPath' -Directory
$kept = Join-Path -Path $omittedPathFolder -ChildPath 'Kept.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $kept
Set-Content -LiteralPath $kept -Value 'Kept'
Push-Location -LiteralPath $omittedPathFolder
try {
$result = @ ( Remove-Item2 -PassThru -ErrorVariable removeErrors -ErrorAction SilentlyContinue )
}
finally {
Pop-Location
}
$removeErrors | Should -BeNullOrEmpty
$result | Should -BeNullOrEmpty
$kept | Should -Exist
$omittedPathFolder | Should -Exist
}
It 'Should remove the file through the <Alias> alias' -ForEach @ (
@ { Alias = 'rm2' }
@ { Alias = 'del2' }
) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name " RemoveAlias $ Alias "
& $Alias -Path $file -ErrorAction Stop
$file | Should -Not -Exist
}
It 'Should write FileNotFound with its category and target for a path that does not exist' {
$missing = Join-Path -Path $sandbox -ChildPath ( 'RemoveMissing-{0}' -f [guid] :: NewGuid ( ) . ToString ( 'N' ) . Substring ( 0 , 8 ) )
Remove-Item2 -Path $missing -ErrorVariable removeErrors -ErrorAction SilentlyContinue
$removeErrors | Should -HaveCount 1
$removeErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'FileNotFound,*'
$removeErrors [ 0 ] . CategoryInfo . Category | Should -Be 'ObjectNotFound'
$removeErrors [ 0 ] . TargetObject | Should -Be $missing
}
It 'Should write nothing for a successful removal without -PassThru' {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveQuiet'
$result = @ ( Remove-Item2 -Path $file -ErrorAction Stop )
$result | Should -BeNullOrEmpty
$file | Should -Not -Exist
}
# The generic catch of RemoveItem2.cs (category NotSpecified) is reached by anything that is not an IOException;
# a Deny ACE for Delete makes the file system refuse with an UnauthorizedAccessException, a sibling of
# IOException, not a subclass of it, so it falls through to that generic branch instead of the InvalidData one
# that the locked-file and non-empty-folder cases already cover (Remove-Item2.Tests.ps1:36,97). NTFS allows a
# delete when either the file itself allows it or its parent grants DeleteSubdirectoriesAndFiles, so a dedicated
# folder denies the second route as well; otherwise the inherited folder permission of the sandbox still lets the
# delete through despite the Deny entry on the file alone (seen first-hand: no error was written).
It 'Should write DeleteError with category NotSpecified when a Deny ACE refuses the delete' {
$deniedFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveAccessDeniedFolder' -Directory
$file = Join-Path -Path $deniedFolder -ChildPath 'Denied.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
Set-Content -LiteralPath $file -Value 'Denied'
Add-TestDenyRule -Sandbox $sandbox -Path $deniedFolder -Rights @ { 'S-1-1-0' = 'DeleteSubdirectoriesAndFiles' }
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @ { 'S-1-1-0' = 'Delete' }
Remove-Item2 -Path $file -ErrorVariable removeErrors -ErrorAction SilentlyContinue
$removeErrors | Should -HaveCount 1
$removeErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'DeleteError,*'
$removeErrors [ 0 ] . CategoryInfo . Category | Should -Be 'NotSpecified'
$removeErrors [ 0 ] . TargetObject | Should -Be $file
$removeErrors [ 0 ] . Exception | Should -BeOfType [System.UnauthorizedAccessException]
$file | Should -Exist
}
}
Describe 'Test-Path2 with several path shapes' {
It 'Should accept several piped objects and bind -PathType by property name for each' {
$pipedFile = New-TestSandboxItem -Sandbox $sandbox -Name 'PipedPathTypeFile'
$pipedFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'PipedPathTypeFolder' -Directory
$requests = @ (
[pscustomobject] @ { FullName = $pipedFile ; PathType = 'Leaf' }
[pscustomobject] @ { FullName = $pipedFile ; PathType = 'Container' }
[pscustomobject] @ { FullName = $pipedFolder ; PathType = 'Container' }
)
$result = @ ( $requests | Test-Path2 -ErrorAction Stop )
$result -join ',' | Should -Be 'True,False,True'
}
It 'Should find an item whose name contains brackets' {
$bracketFile = Join-Path -Path $sandbox -ChildPath 'TestPathBracket[2].txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $bracketFile
Set-Content -LiteralPath $bracketFile -Value 'Bracket'
Test-Path2 -Path $bracketFile -PathType Leaf -ErrorAction Stop | Should -BeTrue
}
It 'Should find an item through a UNC path of the administrative share' -Skip : ( -not $canUseAdminShare ) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'UncPathFile'
$uncPath = ConvertTo-TestAdminSharePath -Sandbox $sandbox -Path $file
Test-Path2 -Path $uncPath -PathType Leaf -ErrorAction Stop | Should -BeTrue
}
}
Describe 'Get-FileHash2 binds -Algorithm by property name' {
It 'Should hash with the algorithm bound by property name from the pipeline' {
# Stored in a variable, like FileHash.Tests.ps1 and the MD5 call above: PSScriptAnalyzer flags a literal
# weak-algorithm argument, not one that comes from a variable.
$propertyAlgorithm = 'MD5'
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'HashAlgorithmByProperty'
Set-Content -LiteralPath $file -Value 'AlgorithmPropertyContent'
$request = [pscustomobject] @ { FullName = $file ; Algorithm = $propertyAlgorithm }
$result = $request | Get-FileHash2 -ErrorAction Stop
$result . Algorithm | Should -Be $propertyAlgorithm
$result . Hash | Should -BeExactly ( Get-FileHash -LiteralPath $file -Algorithm $propertyAlgorithm ) . Hash
}
}
test(items,owner,access): bind four parameters by property name
The coverage record of Handoff 5 listed getters of parameters that take
pipeline input by property name and that no test bound that way: -Destination
of Copy-Item2 and Move-Item2, -Account of Set-NTFSOwner, and -Account of
Get-NTFSEffectiveAccess (the getters of AccessType, AuditFlags, and the scope
and flag parameters of the access and audit cmdlets, and -Algorithm of
Get-FileHash2, are bound by tests now). PowerShell calls the getter of such a
parameter once per command when a piped object binds it, so a test has to
pipe an object that has the property.
Each test pipes two objects that name different values and checks that each
item gets its own: two destinations for the copied and the moved file
(Copy-Item2 and Move-Item2, read back with Get-Content and Test-Path), two
owners that only the Restore privilege can assign (Set-NTFSOwner, read back
with Get-Acl; elevated only), and the effective access of Everyone, which an
allow entry grants, and of Guests, which a deny entry takes away
(Get-NTFSEffectiveAccess).
Tests only; no product change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
Describe 'Copy-Item2 and Move-Item2 bind -Destination by property name' {
# A piped object binds -Destination by its own property, so two objects can go to two destinations in one call.
It '<Command> should put each piped item in the destination that its own object names' -ForEach @ (
@ { Command = 'Copy-Item2' }
@ { Command = 'Move-Item2' }
) {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name " DestinationByProperty $ Command " -Directory
$destinationOne = Join-Path -Path $folder -ChildPath 'One'
$destinationTwo = Join-Path -Path $folder -ChildPath 'Two'
$first = Join-Path -Path $folder -ChildPath 'First.txt'
$second = Join-Path -Path $folder -ChildPath 'Second.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $destinationOne , $destinationTwo , $first , $second
New-Item -ItemType Directory -Path $destinationOne , $destinationTwo | Out-Null
Set-Content -LiteralPath $first -Value 'First'
Set-Content -LiteralPath $second -Value 'Second'
$requests = @ (
[pscustomobject] @ { Path = $first ; Destination = $destinationOne }
[pscustomobject] @ { Path = $second ; Destination = $destinationTwo }
)
$result = @ ( $requests | & $Command -PassThru $true -ErrorAction Stop )
$copiedFirst = Join-Path -Path $destinationOne -ChildPath 'First.txt'
$copiedSecond = Join-Path -Path $destinationTwo -ChildPath 'Second.txt'
$result | Should -HaveCount 2
$result [ 0 ] . FullName | Should -Be $copiedFirst
$result [ 1 ] . FullName | Should -Be $copiedSecond
Get-Content -LiteralPath $copiedFirst | Should -Be 'First'
Get-Content -LiteralPath $copiedSecond | Should -Be 'Second'
( Join-Path -Path $destinationOne -ChildPath 'Second.txt' ) | Should -Not -Exist
( Join-Path -Path $destinationTwo -ChildPath 'First.txt' ) | Should -Not -Exist
if ( $Command -eq 'Copy-Item2' ) {
$first | Should -Exist
$second | Should -Exist
}
else {
$first | Should -Not -Exist
$second | Should -Not -Exist
}
}
}
test(items): close budget gaps for item, link, disk-space, and privilege cmdlets
Adds pipeline/positional binding, long-path, bracket/wildcard literal-path,
drive-root, omitted-path-default, and error category/target coverage for
Copy-Item2, Get-ChildItem2, Get-Item2, Move-Item2, Remove-Item2, Test-Path2,
Get-FileHash2, and Get-DiskSpace; positional binding, FileInfo pipeline input,
and share-error category coverage for New-NTFSHardLink and Get-NTFSHardLink;
positional binding and folder LinkType/target coverage for
New-NTFSSymbolicLink; and -PassThru privilege-state coverage for
Enable-Privileges and Disable-Privileges. No product code changed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
2 weeks ago
Describe 'Get-DiskSpace free-space values and validation error' {
It 'Should report free-space values close to an independent .NET read for the system drive' {
$before = New-Object -TypeName 'System.IO.DriveInfo' -ArgumentList $env:SystemDrive
$result = Get-DiskSpace -DriveLetter $env:SystemDrive -ErrorAction Stop
$after = New-Object -TypeName 'System.IO.DriveInfo' -ArgumentList $env:SystemDrive
# Free space can change between two live reads of a system drive under normal system activity, so the
# comparison admits a tolerance around the values read immediately before and after the call, instead of
# exact equality; the tolerance is still tight enough to catch a swapped or a wrong field.
$tolerance = 500MB
$result . TotalNumberOfFreeBytes | Should -BeGreaterOrEqual ( [Math] :: Min ( $before . TotalFreeSpace , $after . TotalFreeSpace ) - $tolerance )
$result . TotalNumberOfFreeBytes | Should -BeLessOrEqual ( [Math] :: Max ( $before . TotalFreeSpace , $after . TotalFreeSpace ) + $tolerance )
$result . FreeBytesAvailable | Should -BeGreaterOrEqual ( [Math] :: Min ( $before . AvailableFreeSpace , $after . AvailableFreeSpace ) - $tolerance )
$result . FreeBytesAvailable | Should -BeLessOrEqual ( [Math] :: Max ( $before . AvailableFreeSpace , $after . AvailableFreeSpace ) + $tolerance )
}
# PowerShell's own parameter-validation engine raises this error before the cmdlet runs, so unlike the cmdlet's
# own WriteError calls it carries no target object; this pins that engine behavior as observed, not assumed.
It 'Should write the validation error for a bad -DriveLetter with its category, and no target object' {
$caught = $null
try {
Get-DiskSpace -DriveLetter 'C' -ErrorAction Stop
}
catch {
$caught = $_
}
$caught . FullyQualifiedErrorId | Should -Be 'ParameterArgumentValidationError,NTFSSecurity.GetDiskSpace'
$caught . CategoryInfo . Category | Should -Be 'InvalidData'
$caught . TargetObject | Should -BeNullOrEmpty
}
}
Describe 'New-NTFSHardLink positional binding, FileInfo input, and share errors' {
It 'Should bind -Path and -Target positionally' {
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'PositionalHardLinkTarget'
Set-Content -LiteralPath $target -Value 'PositionalTarget'
$link = Join-Path -Path $sandbox -ChildPath 'PositionalHardLinkLink.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
New-NTFSHardLink $link $target -ErrorAction Stop
$link | Should -Exist
Get-Content -LiteralPath $link | Should -Be 'PositionalTarget'
}
# Target has no FullName alias, so a FileInfo object can reach it only by value, through the
# FileSystemPathTransformationAttribute of BaseCmdlets.cs, which reads .FullName for a FileSystemInfo argument.
# -Path is bound explicitly by name here, which keeps it out of the pipeline-by-value binding so that the piped
# FileInfo binds to -Target only.
It 'Should accept a FileInfo object piped by value for -Target' {
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'FileInfoBoundTarget'
Set-Content -LiteralPath $target -Value 'FileInfoTarget'
$targetItem = Get-Item2 -Path $target -ErrorAction Stop
$link = Join-Path -Path $sandbox -ChildPath 'FileInfoBoundLink.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
$targetItem | New-NTFSHardLink -Path $link -ErrorAction Stop
$link | Should -Exist
Get-Content -LiteralPath $link | Should -Be 'FileInfoTarget'
}
# The existing share test (Links.Tests.ps1:150) asserts only the ID of this error.
It '-PassThru should write GetHardLinkError with category ReadError and the link path as target on a network share' -Skip : ( -not $canUseAdminShare ) {
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'ShareCategoryTarget'
$link = Join-Path -Path $sandbox -ChildPath 'ShareCategoryLink.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
$sharePath = ConvertTo-TestAdminSharePath -Sandbox $sandbox -Path $link
$shareTarget = ConvertTo-TestAdminSharePath -Sandbox $sandbox -Path $target
$result = @ ( New-NTFSHardLink -Path $sharePath -Target $shareTarget -PassThru -ErrorVariable linkErrors -ErrorAction SilentlyContinue )
$link | Should -Exist
$linkErrors | Should -HaveCount 1
$linkErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'GetHardLinkError,*'
$linkErrors [ 0 ] . CategoryInfo . Category | Should -Be 'ReadError'
$linkErrors [ 0 ] . TargetObject | Should -Be $sharePath
$result | Should -BeNullOrEmpty
}
}
Describe 'Get-NTFSHardLink category on a network share' {
# The existing share test (Links.Tests.ps1:247) asserts the ID and the target but not the category.
It 'Should write GetHardLinkError with category ReadError for a file on a network share' -Skip : ( -not $canUseAdminShare ) {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'HardLinkShareCategoryFile'
$sharePath = ConvertTo-TestAdminSharePath -Sandbox $sandbox -Path $file
Get-NTFSHardLink -Path $sharePath -ErrorVariable linkErrors -ErrorAction SilentlyContinue
$linkErrors | Should -HaveCount 1
$linkErrors [ 0 ] . FullyQualifiedErrorId | Should -BeLike 'GetHardLinkError,*'
$linkErrors [ 0 ] . CategoryInfo . Category | Should -Be 'ReadError'
$linkErrors [ 0 ] . TargetObject | Should -Be $sharePath
}
}
Describe 'New-NTFSSymbolicLink positional binding and folder links' {
It 'Should bind -Path and -Target positionally' -Skip : ( -not $canCreateSymbolicLinks ) {
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'PositionalSymbolicTarget'
Set-Content -LiteralPath $target -Value 'PositionalSymbolicTarget'
$link = Join-Path -Path $sandbox -ChildPath 'PositionalSymbolicLink.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
New-NTFSSymbolicLink $link $target -ErrorAction Stop
( Get-Item -LiteralPath $link -Force ) . LinkType | Should -Be 'SymbolicLink'
Get-Content -LiteralPath $link | Should -Be 'PositionalSymbolicTarget'
}
# The existing folder-link test (Links.Tests.ps1:304) checks only the Directory attribute and reachability; only
# the file-link test (Links.Tests.ps1:297) asserts LinkType and the resolved target.
It 'Should set LinkType and the resolved target for a link to a folder' -Skip : ( -not $canCreateSymbolicLinks ) {
$targetFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'FolderLinkTypeTarget' -Directory
$link = Join-Path -Path $sandbox -ChildPath 'FolderLinkType'
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
New-NTFSSymbolicLink -Path $link -Target $targetFolder -ErrorAction Stop
( Get-Item -LiteralPath $link -Force ) . LinkType | Should -Be 'SymbolicLink'
# Windows PowerShell returns the target as an array, PowerShell 7 as a string (Links.Tests.ps1:286).
@ ( ( Get-Item -LiteralPath $link -Force ) . Target ) [ 0 ] | Should -Be $targetFolder
}
}
Describe 'Enable-Privileges -PassThru privilege states' {
AfterEach {
# Leaves the process token as the rest of the file expects it, regardless of which assertion above failed.
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
}
# Only Get-Privileges was used as the read-back before; this reads the four states directly from the -PassThru
# objects and cross-checks them against whoami /priv, an independent source outside the module.
It 'Should report Enabled for TakeOwnership, Restore, Backup, and Security in -PassThru, matching whoami /priv' -Skip : ( -not $isElevated ) {
$result = @ ( Enable-Privileges -PassThru -ErrorAction Stop )
$whoamiPrivileges = & whoami . exe / priv / fo csv | Select-Object -Skip 1 | ConvertFrom-Csv -Header 'Name' , 'Description' , 'State'
foreach ( $name in 'TakeOwnership' , 'Restore' , 'Backup' , 'Security' ) {
$entry = @ ( $result | Where-Object -Property Privilege -EQ -Value $name )
$entry | Should -HaveCount 1 -Because " Enable-Privileges -PassThru should list $ name "
$entry [ 0 ] . PrivilegeState | Should -Be 'Enabled'
$whoamiEntry = @ ( $whoamiPrivileges | Where-Object -Property Name -EQ -Value ( 'Se{0}Privilege' -f $name ) )
# The State column is localized; only the rows that whoami prints in English are compared (Get-Privileges
# compared with an independent source, above).
if ( $whoamiEntry . Count -gt 0 -and $whoamiEntry [ 0 ] . State -in 'Enabled' , 'Disabled' ) {
$whoamiEntry [ 0 ] . State | Should -Be 'Enabled'
}
}
}
}
Describe 'Disable-Privileges -PassThru privilege states' {
It 'Should report Disabled for TakeOwnership, Restore, and Backup in -PassThru after a successful disable' -Skip : ( -not $isElevated ) {
Enable-Privileges -ErrorAction Stop
$result = @ ( Disable-Privileges -PassThru -ErrorAction Stop )
foreach ( $name in 'TakeOwnership' , 'Restore' , 'Backup' ) {
$entry = @ ( $result | Where-Object -Property Privilege -EQ -Value $name )
$entry | Should -HaveCount 1 -Because " Disable-Privileges -PassThru should list $ name "
$entry [ 0 ] . PrivilegeState | Should -Be 'Disabled'
}
}
}
# NOT DONE: "the Removed PrivilegeState value" (WORKLIST row 14) is not reachable through Get-Privileges. A probe
# (elevated, PowerShell 7) that removed the Backup privilege of a child process and compared Get-Privileges before
# and after showed the entry disappear from the list entirely instead of remaining with PrivilegeState=Removed:
# BEFORE: ...,Backup=0,Restore=0,... AFTER: ...,Restore=0,... (Backup is gone, not listed as Removed)
# Privileges.GetPrivileges(accessTokenHandle) (ProcessPrivileges\Privileges.cs) builds its collection only from what
# GetTokenInformation(TokenPrivileges) currently returns, which drops an entry once SE_PRIVILEGE_REMOVED takes effect
# (matching the documented Win32 behavior of that attribute); the Removed state is produced only by the unrelated
# Privileges.GetPrivilegeAttributes(privilege, collection) lookup helper, which Get-Privileges never calls. No input
# makes Get-Privileges itself emit a Removed entry, so this item is left out instead of pinning a test that cannot
# exercise the branch it names; this is a structural gap of the same kind as the "Limit" rows, not a defect of a
# cmdlet, so nothing is reported under DEFECTS.