# encoding: ascii
# api: csharp
# title: Get-HardlinkInfo
# description: An autoload function script (save as Get-HardlinkInfo.ps1, after you use it the function will be in global namespace) that returns hardlink information (siblings) for the given file. Most recent version lives at https://github.com/PProvost/dotfiles/blob/master/powershell/Get-HardlinkInfo.ps1. Can be used in a custom types.ps1xml and format.ps1xml to display whether a file is a hardlink in your directory listings.
# version: 0.1
# type: script
# author: Peter Provost
# license: CC0
# x-poshcode-id: 3293
# x-archived: 2016-03-02T15:53:08
# x-published: 2013-03-19T00:23:00
#
#
# .SYNOPSIS
# 	Returns information about NTFS hard links. Only available on Windows Vista or later.
#
# .DESCRIPTION
# 	This script can be used to determine how many alternate file names (i.e. hard link 
# 	siblings) a particular file has or to return all hard link siblings as FileInfo 
# 	objects. It uses the Win32 APIs FindFirstFileNameW and FindNextFileNameW to return 
# 	the hard link siblings, and these APIs are only available on Windows Vista or later.
#
# .PARAMETER $filepath
# 	The file path you want to get hard link info for
#
# .PARAMETER $count
# 	Will cause the script to return the count instead of the filenames. This is more
# 	efficient than using measure-object on the returned FileInfo objects.
#
# .PARAMETER $all
# 	When specified, will cause the script to return the FileInfo for the provided
# 	filepath as well. When not specified, the file specified by the filepath 
# 	parameter is not returned.
param( [string] $filepath, [switch] $count, [switch] $all )

$typeDef = @'
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;

namespace HardLinkEnumerator
{
    public static class Kernel32Api
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct BY_HANDLE_FILE_INFORMATION
        {
            public uint FileAttributes;
            public FILETIME CreationTime;
            public FILETIME LastAccessTime;
            public FILETIME LastWriteTime;
            public uint VolumeSerialNumber;
            public uint FileSizeHigh;
            public uint FileSizeLow;
            public uint NumberOfLinks;
            public uint FileIndexHigh;
            public uint FileIndexLow;
        }

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern SafeFileHandle CreateFile(
            string lpFileName,
            [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
            [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
            IntPtr lpSecurityAttributes,
            [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
            [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
            IntPtr hTemplateFile);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool GetFileInformationByHandle(SafeFileHandle handle, out BY_HANDLE_FILE_INFORMATION lpFileInformation);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool CloseHandle(SafeHandle hObject);

        [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
        static extern IntPtr FindFirstFileNameW(
            string lpFileName,
            uint dwFlags,
            ref uint stringLength,
            StringBuilder fileName);

        [DllImport("kernel32.dll", SetLastError = true, CharSet=CharSet.Unicode)]
        static extern bool FindNextFileNameW(
            IntPtr hFindStream,
            ref uint stringLength,
            StringBuilder fileName);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool FindClose(IntPtr fFindHandle);
        
        [DllImport("kernel32.dll")]
        static extern bool GetVolumePathName(string lpszFileName, 
            [Out] StringBuilder lpszVolumePathName, uint cchBufferLength);
        
        [DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
        static extern bool PathAppend([In, Out] StringBuilder pszPath, string pszMore);
        
        public static int GetFileLinkCount(string filepath)
        {
            int result = 0;
            SafeFileHandle handle = CreateFile(filepath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero);
            BY_HANDLE_FILE_INFORMATION fileInfo = new BY_HANDLE_FILE_INFORMATION();
            if (GetFileInformationByHandle(handle, out fileInfo))
                result = (int)fileInfo.NumberOfLinks;
            CloseHandle(handle);
            return result;
        }

        public static string[] GetFileSiblingHardLinks(string filepath)
        {
            List<string> result = new List<string>();
            uint stringLength = 256;
            StringBuilder sb = new StringBuilder(256);
            GetVolumePathName(filepath, sb, stringLength);
            string volume = sb.ToString();
            sb.Length = 0; stringLength = 256;
            IntPtr findHandle = FindFirstFileNameW(filepath, 0, ref stringLength, sb);
            if (findHandle.ToInt32() != -1)
            {
                do
                {
                    StringBuilder pathSb = new StringBuilder(volume, 256);
                    PathAppend(pathSb, sb.ToString());
                    result.Add(pathSb.ToString());
                    sb.Length = 0; stringLength = 256;
                } while (FindNextFileNameW(findHandle, ref stringLength, sb));
                FindClose(findHandle);
                return result.ToArray();
            }
            return null;
        }

    }
}
'@

$type = Add-Type -TypeDefinition $typeDef

function global:Get-HardlinkInfo( [string] $filepath, [switch] $count, [switch] $all ) {
	$filepath = resolve-path $filepath

	if ($count) {
		[HardLinkEnumerator.Kernel32Api]::GetFileLinkCount($filepath)
	} else {
		$links = [HardLinkEnumerator.Kernel32Api]::GetFileSiblingHardLinks($filepath)
		if ($all) {
			$links | get-childitem 
		} else {
			$links | ? { $_ -ne $filepath } | get-childitem 
		}
	}

}

Get-HardLinkInfo @PSBoundParameters
