Joey
Joey

Reputation: 354774

Find out whether a file is a symbolic link in PowerShell

I am having a PowerShell script which is walking a directory tree, and sometimes I have auxiliary files hardlinked there which should not be processed. Is there an easy way of finding out whether a file (that is, System.IO.FileInfo) is a hard link or not?

If not, would it be easier with symbolic links (symlinks)?

Upvotes: 53

Views: 56113

Answers (10)

user1857592
user1857592

Reputation: 13

My two cents on useful functions for problems in this space with a short set of examples at the end. These are in my profile:

function Resolve-RealPath {
[CmdletBinding(PositionalBinding = $false)]
param(
    [ValidateNotNullOrEmpty()]
    [Parameter(Position = 1)]
    [string[]]
    $Path,

    [switch]
    $Relative
)
foreach ($item in Get-Item -Force $Path) {
    try {
        if ($null -eq $item.LinkType) {
            Resolve-Path -ErrorAction Stop -Relative:$Relative $item    
        }
        elseif ([Path]::IsPathRooted($item.LinkTarget)) {
            Resolve-Path -ErrorAction Stop -Relative:$Relative $item.LinkTarget
        }
        else {
            Resolve-Path -ErrorAction Stop -Relative:$Relative (Join-Path $item.Directory $item.LinkTarget)
        }
    }
    catch {
        Write-Error $_
    }
}

}

function Test-RealPath {
    [CmdletBinding(PositionalBinding = $false)]
    param(
        [ValidateNotNullOrEmpty()]
        [Parameter(Position = 1)]
        [string[]]
        $Path
    )
    foreach ($item in $Path) {
        try {
            if ($null -eq $item.LinkType) {
                Test-Path -ErrorAction Stop $item
            }
            elseif ([Path]::IsPathRooted($item.LinkTarget)) {
                Test-Path -ErrorAction Stop $item.LinkTarget
            }
            else {
                Test-Path -ErrorAction Stop (Join-Path $item.Directory $item.LinkTarget)
            }
        }
        catch {
            Write-Error $_
        }
    }
}

function Test-ReparsePoint {
    [CmdletBinding(PositionalBinding = $false)]
    param(
        [ValidateNotNullOrEmpty()]
        [Parameter(Position = 1)]
        [string[]]
        $Path
    )
    foreach ($item in $Path) {
        try {
            $null -eq (Get-Item $item).LinkType
        }
        catch {
            Write-Error $_
        }
    }
}

# Example 1
Get-ChildItem -Recurse . | Where-Object { Test-ReparsePoint $_ } | ...

# Example 2
Get-ChildItem -Attributes ReparsePoint -Recurse . | Where-Object { Test-RealPath $_ } | ...

# Example 3
Get-ChildItem -Attributes ReparsePoint -Recurse . | Where-Object { -not (Test-RealPath $_) } | Remove-Item

@Joey, do these cover your scenarios?

Upvotes: 0

DarkStar
DarkStar

Reputation: 595

I ran into an issue with .Target being null (on OneDrive files) and I wanted to differentiate between SYMLINKD and SYMLINK, so I came up with this variation (doesn't handle HardLink). My goal was to capture what was there so I could recreate them elsewhere. Note that ? = Where-Object and % = Foreach-Object.

### ReparsePoint + Directory + Junction = mklink /j 
### ReparsePoint + Directory + SymbolicLink = mklink /d 
### ReparsePoint + SymbolicLink = mklink 

"cd $( $pwd.Path )"; Get-ChildItem | ? { $_.Attributes -match 'ReparsePoint' -and $_.Target -ne $null } | % {
    $linktype = $_.LinkType
    $target = Resolve-Path -Path $_.Target
    if ($_.Attributes -match 'Directory') {
        if ($linktype -eq "Junction") {
            "mklink /j `"$($_.Name)`" `"$target`""
        } else {
            "mklink /d `"$($_.Name)`" `"$target`""
        }
    } else {
        "mklink `"$($_.Name)`" `"$target`""
    }
}

Upvotes: 0

Kevin Holtkamp
Kevin Holtkamp

Reputation: 468

Just want to add my own two cents, this is a oneliner function which works perfectly fine for me:

Function Test-Symlink($Path){
    ((Get-Item $Path).Attributes.ToString() -match "ReparsePoint")
}

Upvotes: 4

Luan Vitor
Luan Vitor

Reputation: 83

here is a one-liner that checks one file $FilePath and returns if it is a symlink or not, works for files and directories

if((Get-ItemProperty $FilePath).LinkType){"symboliclink"}else{"normal path"}

Upvotes: 2

jzsf-sz
jzsf-sz

Reputation: 353

For those that want to check if a resource is a hardlink or symlink:

(Get-Item ".\some_resource").LinkType -eq "HardLink"

(Get-Item ".\some_resource").LinkType -eq "SymbolicLink"

Upvotes: 18

Keith Hill
Keith Hill

Reputation: 2389

Try this:

function Test-ReparsePoint([string]$path) {
  $file = Get-Item $path -Force -ea SilentlyContinue
  return [bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint)
}

It is a pretty minimal implementation, but it should do the trick. Note that this doesn't distinguish between a hard link and a symbolic link. Underneath, they both just take advantage of NTFS reparse points, IIRC.

Upvotes: 51

Anton Krouglov
Anton Krouglov

Reputation: 3409

If you have Powershell 5+ the following one-liner recursively lists all file hardlinks, directory junctions and symbolic links and their targets starting from d:\Temp\:

dir 'd:\Temp' -recurse -force | ?{$_.LinkType} | select FullName,LinkType,Target

Output:

FullName                                LinkType     Target
--------                                --------     ------
D:\Temp\MyJunctionDir                   Junction     {D:\exp\junction_target_dir}
D:\Temp\MySymLinkDir                    SymbolicLink {D:\exp\symlink_target_dir}
D:\Temp\MyHardLinkFile.txt              HardLink     {D:\temp\MyHardLinkFile2.txt, D:\exp\hlink_target.xml}
D:\Temp\MyHardLinkFile2.txt             HardLink     {D:\temp\MyHardLinkFile.txt, D:\exp\hlink_target.xml}
D:\Temp\MySymLinkFile.txt               SymbolicLink {D:\exp\symlink_target.xml}
D:\Temp\MySymLinkDir\MySymLinkFile2.txt SymbolicLink {D:\temp\normal file.txt}

If you care about multiple targets for hardlinks use this variation which lists targets tab-separated:

dir 'd:\Temp' -recurse -force | ?{$_.LinkType} | select FullName,LinkType,@{ Name = "Targets"; Expression={$_.Target -join "`t"} }

You may need administrator privileges to run this script on say C:\.

Upvotes: 50

k3yz101
k3yz101

Reputation: 529

Utilize Where-Object to search for the ReparsePoint file attribute.

Get-ChildItem | Where-Object { $_.Attributes -match "ReparsePoint" }

Upvotes: 22

b_ball
b_ball

Reputation: 19

The following PowerShell script will list all the files in a directory or directories with the -recurse switch. It will list the name of the file, whether it is a regular file or a hardlinked file, and the size, separated by colons.

It must be run from the PowerShell command line. It doesn't matter which directory you run it from as that is set in the script.

It uses the fslink utility shipped with Windows and runs that against each file using the hardlink and list switches and counts the lines of output. If two or greater it is a hardlinked file.

You can of course change the directory the search starts from by changing the c:\windows\system in the command. Also, the script simply writes the results to a file, c:\hardlinks.txt. You can change the name or simply delete everything from the > character on and it will output to the screen.

Get-ChildItem -path C:\Windows\system -file -recurse -force | 
    foreach-object {
        if ((fsutil hardlink list $_.fullname).count -ge 2) {
            $_.PSChildname + ":Hardlinked:" + $_.Length
        } else {
            $_.PSChildname + ":RegularFile:" + $_.Length
        }
    } > c:\hardlinks.txt

Upvotes: 1

Cheeso
Cheeso

Reputation: 192617

My results on Vista, using Keith Hill's powershell script to test symlinks and hardlinks:

c:\markus\other>mklink symlink.doc \temp\2006rsltns.doc
symbolic link created for symlink.doc <<===>> \temp\2006rsltns.doc

c:\markus\other>fsutil hardlink create HARDLINK.doc  \temp\2006rsltns.doc
Hardlink created for c:\markus\other\HARDLINK.doc <<===>> c:\temp\2006rsltns.doc

c:\markus\other>dir
 Volume in drive C has no label.
 Volume Serial Number is C8BC-2EBD

 Directory of c:\markus\other

02/12/2010  05:21 PM    <DIR>          .
02/12/2010  05:21 PM    <DIR>          ..
01/10/2006  06:12 PM            25,088 HARDLINK.doc
02/12/2010  05:21 PM    <SYMLINK>      symlink.doc [\temp\2006rsltns.doc]
               2 File(s)         25,088 bytes
               2 Dir(s)   6,805,803,008 bytes free

c:\markus\other>powershell \script\IsSymLink.ps1 HARDLINK.doc
False

c:\\markus\other>powershell \script\IsSymLink.ps1 symlink.doc
True

It shows that symlinks are reparse points, and have the ReparsePoint FileAttribute bit set, while hardlinks do not.

Upvotes: 2

Related Questions