|
|
|
|
<#
|
|
|
|
|
Tests the link cmdlets of the module built in NTFSSecurity\bin\Release in a sandbox folder.
|
|
|
|
|
#>
|
|
|
|
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
|
|
|
|
|
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
|
|
|
|
|
)]
|
|
|
|
|
param ()
|
|
|
|
|
|
test: cover the link cmdlets
Add 18 tests for New-NTFSHardLink, Get-NTFSHardLink, and
New-NTFSSymbolicLink from the contracts of their cmdlet pages: output with
and without -PassThru, relative paths, the refusal of an existing -Path and
of a folder as the target of a hard link, the non-terminating errors for
missing paths, and the error 1314 without the right to create symbolic
links, compared by its HRESULT because the message is localized.
The tests that need SeCreateSymbolicLinkPrivilege skip without it, and the
test without it skips with it. Elevated and as a basic user, in Windows
PowerShell 5.1 and PowerShell 7, all 20 link tests pass, and each one runs
in at least one of the four configurations.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
BeforeDiscovery {
|
|
|
|
|
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
|
|
|
|
|
$canCreateSymbolicLinks = Test-PrivilegeHeld -Name 'SeCreateSymbolicLinkPrivilege'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BeforeAll {
|
|
|
|
|
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
|
|
|
|
|
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release\NTFSSecurity.psd1'
|
|
|
|
|
Import-Module -Name $modulePath -Force -ErrorAction Stop
|
|
|
|
|
$sandbox = New-TestSandbox -Name 'Links'
|
|
|
|
|
Push-Location -LiteralPath $sandbox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AfterAll {
|
|
|
|
|
Pop-Location
|
|
|
|
|
Remove-TestSandbox -Sandbox $sandbox
|
|
|
|
|
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Describe 'New-NTFSHardLink' {
|
|
|
|
|
It 'Should create a hard link to a file' {
|
|
|
|
|
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'Target'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'Link.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
|
|
|
|
|
|
|
|
|
|
New-NTFSHardLink -Path $link -Target $target -ErrorAction Stop
|
|
|
|
|
|
|
|
|
|
$link | Should -Exist
|
|
|
|
|
Get-Content -LiteralPath $link | Should -Be (Get-Content -LiteralPath $target)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Before 5.0.0, the error said "The target path exist" for a target that did not exist.
|
|
|
|
|
It 'Should report that a missing target does not exist' {
|
|
|
|
|
$missing = Join-Path -Path $sandbox -ChildPath 'Missing.txt'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'MissingLink.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing, $link
|
|
|
|
|
|
|
|
|
|
{ New-NTFSHardLink -Path $link -Target $missing -ErrorAction Stop } | Should -Throw -ExpectedMessage '*does not exist*'
|
|
|
|
|
$link | Should -Not -Exist
|
|
|
|
|
}
|
test: cover the link cmdlets
Add 18 tests for New-NTFSHardLink, Get-NTFSHardLink, and
New-NTFSSymbolicLink from the contracts of their cmdlet pages: output with
and without -PassThru, relative paths, the refusal of an existing -Path and
of a folder as the target of a hard link, the non-terminating errors for
missing paths, and the error 1314 without the right to create symbolic
links, compared by its HRESULT because the message is localized.
The tests that need SeCreateSymbolicLinkPrivilege skip without it, and the
test without it skips with it. Elevated and as a basic user, in Windows
PowerShell 5.1 and PowerShell 7, all 20 link tests pass, and each one runs
in at least one of the four configurations.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
|
|
|
|
|
|
|
|
It 'Should write nothing without -PassThru' {
|
|
|
|
|
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'Quiet'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'QuietLink.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
|
|
|
|
|
|
|
|
|
|
$result = @(New-NTFSHardLink -Path $link -Target $target -ErrorAction Stop)
|
|
|
|
|
|
|
|
|
|
$result | Should -BeNullOrEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should return every name of the file with -PassThru' {
|
|
|
|
|
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'Names'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'NamesLink.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
|
|
|
|
|
|
|
|
|
|
$result = @(New-NTFSHardLink -Path $link -Target $target -PassThru -ErrorAction Stop)
|
|
|
|
|
|
|
|
|
|
$result | Should -HaveCount 2
|
|
|
|
|
$result | ForEach-Object -Process { $_ | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo] }
|
|
|
|
|
($result.FullName | Sort-Object) -join '|' | Should -Be ((@($target, $link) | Sort-Object) -join '|')
|
|
|
|
|
$result[0].Mode | Should -Not -BeNullOrEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should give both names the same data' {
|
|
|
|
|
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'Shared'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'SharedLink.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
|
|
|
|
|
New-NTFSHardLink -Path $link -Target $target -ErrorAction Stop
|
|
|
|
|
|
|
|
|
|
Set-Content -LiteralPath $link -Value 'Changed through the link'
|
|
|
|
|
|
|
|
|
|
Get-Content -LiteralPath $target | Should -Be 'Changed through the link'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should resolve relative paths against the current location' {
|
|
|
|
|
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'Relative'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'RelativeLink.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
|
|
|
|
|
|
|
|
|
|
New-NTFSHardLink -Path 'RelativeLink.txt' -Target (Split-Path -Path $target -Leaf) -ErrorAction Stop
|
|
|
|
|
|
|
|
|
|
$link | Should -Exist
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should refuse an existing -Path and leave it unchanged' {
|
|
|
|
|
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'ExistingTarget'
|
|
|
|
|
$existing = New-TestSandboxItem -Sandbox $sandbox -Name 'Existing'
|
|
|
|
|
Set-Content -LiteralPath $existing -Value 'Existing'
|
|
|
|
|
|
|
|
|
|
{ New-NTFSHardLink -Path $existing -Target $target -ErrorAction Stop } | Should -Throw -ExpectedMessage '*already exist*'
|
|
|
|
|
|
|
|
|
|
Get-Content -LiteralPath $existing | Should -Be 'Existing'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should refuse a folder as -Target and create no link' {
|
|
|
|
|
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'FolderTarget' -Directory
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'FolderLink.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
|
|
|
|
|
|
|
|
|
|
{ New-NTFSHardLink -Path $link -Target $folder -ErrorAction Stop } | Should -Throw -ExpectedMessage '*not a file*'
|
|
|
|
|
|
|
|
|
|
$link | Should -Not -Exist
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Describe 'Get-NTFSHardLink' {
|
|
|
|
|
It 'Should return the file itself for a file with one name' {
|
|
|
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Single'
|
|
|
|
|
|
|
|
|
|
$result = @(Get-NTFSHardLink -Path $file -ErrorAction Stop)
|
|
|
|
|
|
|
|
|
|
$result | Should -HaveCount 1
|
|
|
|
|
$result[0].FullName | Should -Be $file
|
|
|
|
|
$result[0].Mode | Should -Not -BeNullOrEmpty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should return every name of a file with hard links' {
|
|
|
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Linked'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'LinkedLink.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
|
|
|
|
|
New-NTFSHardLink -Path $link -Target $file -ErrorAction Stop
|
|
|
|
|
|
|
|
|
|
$result = @(Get-NTFSHardLink -Path $link -ErrorAction Stop)
|
|
|
|
|
|
|
|
|
|
($result.FullName | Sort-Object) -join '|' | Should -Be ((@($file, $link) | Sort-Object) -join '|')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should take the files with more than one name from Get-ChildItem2' {
|
|
|
|
|
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'Counted' -Directory
|
|
|
|
|
$file = Join-Path -Path $folder -ChildPath 'File.txt'
|
|
|
|
|
$link = Join-Path -Path $folder -ChildPath 'Link.txt'
|
|
|
|
|
$other = Join-Path -Path $folder -ChildPath 'Other.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $file, $link, $other
|
|
|
|
|
Set-Content -LiteralPath $file, $other -Value 'File'
|
|
|
|
|
New-NTFSHardLink -Path $link -Target $file -ErrorAction Stop
|
|
|
|
|
|
|
|
|
|
$result = @(Get-ChildItem2 -Path $folder -File | Where-Object -Property HardLinkCount -GT -Value 1 | Get-NTFSHardLink -ErrorAction Stop)
|
|
|
|
|
|
|
|
|
|
$result.FullName | Should -Not -Contain $other
|
|
|
|
|
@($result.FullName | Sort-Object -Unique) -join '|' | Should -Be ((@($file, $link) | Sort-Object) -join '|')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should write an error for a path that does not exist and continue with the next path' {
|
|
|
|
|
$missing = Join-Path -Path $sandbox -ChildPath 'MissingHardLink.txt'
|
|
|
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'AfterMissing'
|
|
|
|
|
|
|
|
|
|
$result = @(Get-NTFSHardLink -Path $missing, $file -ErrorVariable linkErrors -ErrorAction SilentlyContinue)
|
|
|
|
|
|
|
|
|
|
$linkErrors | Should -HaveCount 1
|
|
|
|
|
$linkErrors[0].FullyQualifiedErrorId | Should -BeLike 'FileNotFound,*'
|
|
|
|
|
$result.FullName | Should -Be $file
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Describe 'New-NTFSSymbolicLink' {
|
|
|
|
|
BeforeAll {
|
|
|
|
|
function Get-LinkTarget {
|
|
|
|
|
param ([string] $Path)
|
|
|
|
|
|
|
|
|
|
# Windows PowerShell returns the target as an array, PowerShell 7 as a string.
|
|
|
|
|
@((Get-Item -LiteralPath $Path -Force).Target)[0]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should create a link to a file that reads the data of the file' -Skip:(-not $canCreateSymbolicLinks) {
|
|
|
|
|
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'SymbolicTarget'
|
|
|
|
|
Set-Content -LiteralPath $target -Value 'Target'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'SymbolicFile.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
|
|
|
|
|
|
|
|
|
|
$result = @(New-NTFSSymbolicLink -Path $link -Target $target -ErrorAction Stop)
|
|
|
|
|
|
|
|
|
|
$result | Should -BeNullOrEmpty
|
|
|
|
|
(Get-Item -LiteralPath $link -Force).LinkType | Should -Be 'SymbolicLink'
|
|
|
|
|
Get-LinkTarget -Path $link | Should -Be $target
|
|
|
|
|
Get-Content -LiteralPath $link | Should -Be 'Target'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should create a link to a folder through which the files of the folder are reachable' -Skip:(-not $canCreateSymbolicLinks) {
|
|
|
|
|
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'SymbolicFolder' -Directory
|
|
|
|
|
$file = Join-Path -Path $folder -ChildPath 'File.txt'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'SymbolicFolderLink'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $file, $link
|
|
|
|
|
Set-Content -LiteralPath $file -Value 'File'
|
|
|
|
|
|
|
|
|
|
New-NTFSSymbolicLink -Path $link -Target $folder -ErrorAction Stop
|
|
|
|
|
|
|
|
|
|
(Get-Item -LiteralPath $link -Force).Attributes.HasFlag([IO.FileAttributes]::Directory) | Should -BeTrue
|
|
|
|
|
Test-Path2 -Path (Join-Path -Path $link -ChildPath 'File.txt') -PathType Leaf | Should -BeTrue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should return a file object for a link to a file with -PassThru' -Skip:(-not $canCreateSymbolicLinks) {
|
|
|
|
|
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'SymbolicPassThru'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'SymbolicPassThru.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
|
|
|
|
|
|
|
|
|
|
$result = New-NTFSSymbolicLink -Path $link -Target $target -PassThru -ErrorAction Stop
|
|
|
|
|
|
|
|
|
|
$result | Should -BeOfType [Alphaleonis.Win32.Filesystem.FileInfo]
|
|
|
|
|
$result.FullName | Should -Be $link
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Before 5.0.0, the cmdlet returned a file object for a link to a folder.
|
|
|
|
|
It 'Should return a folder object for a link to a folder with -PassThru' -Skip:(-not $canCreateSymbolicLinks) {
|
|
|
|
|
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'SymbolicPassThruFolder' -Directory
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'SymbolicPassThruFolderLink'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
|
|
|
|
|
|
|
|
|
|
$result = New-NTFSSymbolicLink -Path $link -Target $folder -PassThru -ErrorAction Stop
|
|
|
|
|
|
|
|
|
|
$result | Should -BeOfType [Alphaleonis.Win32.Filesystem.DirectoryInfo]
|
|
|
|
|
$result.FullName | Should -Be $link
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should store the absolute path of a relative target' -Skip:(-not $canCreateSymbolicLinks) {
|
|
|
|
|
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'SymbolicRelative'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'SymbolicRelative.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
|
|
|
|
|
|
|
|
|
|
New-NTFSSymbolicLink -Path 'SymbolicRelative.txt' -Target (Split-Path -Path $target -Leaf) -ErrorAction Stop
|
|
|
|
|
|
|
|
|
|
Get-LinkTarget -Path $link | Should -Be $target
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should refuse an existing -Path and leave it unchanged' -Skip:(-not $canCreateSymbolicLinks) {
|
|
|
|
|
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'SymbolicExistingTarget'
|
|
|
|
|
$existing = New-TestSandboxItem -Sandbox $sandbox -Name 'SymbolicExisting'
|
|
|
|
|
Set-Content -LiteralPath $existing -Value 'Existing'
|
|
|
|
|
|
|
|
|
|
{ New-NTFSSymbolicLink -Path $existing -Target $target -ErrorAction Stop } | Should -Throw -ExpectedMessage '*already exist*'
|
|
|
|
|
|
|
|
|
|
(Get-Item -LiteralPath $existing -Force).LinkType | Should -BeNullOrEmpty
|
|
|
|
|
Get-Content -LiteralPath $existing | Should -Be 'Existing'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
It 'Should write an error for a target that does not exist and create no link' {
|
|
|
|
|
$missing = Join-Path -Path $sandbox -ChildPath 'SymbolicMissing.txt'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'SymbolicMissingLink.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing, $link
|
|
|
|
|
|
|
|
|
|
New-NTFSSymbolicLink -Path $link -Target $missing -ErrorVariable linkErrors -ErrorAction SilentlyContinue
|
|
|
|
|
|
|
|
|
|
$linkErrors | Should -HaveCount 1
|
|
|
|
|
$linkErrors[0].FullyQualifiedErrorId | Should -BeLike 'CreateSymbolicLinkError,*'
|
|
|
|
|
Test-Path2 -Path $link | Should -BeFalse
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Windows rejects the link with error 1314 without the "Create symbolic links" user right. Its message is
|
|
|
|
|
# localized, so the test compares the HRESULT of that error.
|
|
|
|
|
It 'Should fail and create no link without the right to create symbolic links' -Skip:$canCreateSymbolicLinks {
|
|
|
|
|
$target = New-TestSandboxItem -Sandbox $sandbox -Name 'SymbolicNoRight'
|
|
|
|
|
$link = Join-Path -Path $sandbox -ChildPath 'SymbolicNoRight.txt'
|
|
|
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $link
|
|
|
|
|
|
|
|
|
|
$thrown = { New-NTFSSymbolicLink -Path $link -Target $target -ErrorAction Stop } | Should -Throw -PassThru
|
|
|
|
|
|
|
|
|
|
$thrown.FullyQualifiedErrorId | Should -BeLike '*,NTFSSecurity.NewSymbolicLink'
|
|
|
|
|
'0x{0:X8}' -f $thrown.Exception.HResult | Should -Be '0x80070522'
|
|
|
|
|
Test-Path2 -Path $link | Should -BeFalse
|
|
|
|
|
}
|
|
|
|
|
}
|