Code: Select all
; showlink.ahk <path>
; reveals all locations hardlinked to path
; path must be complete
; does not show target location; shows first location in active explorer window
; opens new explorer window for all other locations
; has nothing to do if the target location is the only location
; take arguments
try targetpath := A_Args.RemoveAt(1)
catch
MsgBox("No target specified") && ExitApp()
if (SubStr(targetpath, 2, 2) != ":\") ; gotcha: 2 is the length, not the end
MsgBox("Target path is not complete") && ExitApp()
explorer := GetActiveExplorer()
for path in ListLinks(targetpath)
(path != targetpath) && explorer := ShowPath(path, explorer)
; ---------------------------------------------------------------------------- ;
ShowPath(path, explorer)
{
if (explorer)
{
; in an existing explorer window, navigate to and select the specified file
; regex will never fail
explorer.Navigate(RegExMatch(path, "(.*)\\.*", match) ? match[1] : MsgBox("Regex failure"))
; it takes some time to navigate and start seeing files
; https www.mrexcel.com /board/threads/select-a-file-in-file-explorer-from-a-list-of-files-in-excel-in-the-same-fe-window.1084715/ Broken Link for safety
; https://stackoverflow.com/questions/2518257/get-the-selected-file-in-an-explorer-window
; event-based approach didn't work out, let's spin around in circles until we get it
success := false
stopAt := A_TickCount + 3000
loop
try explorer.Document.SelectItem(path, 5) || success := true
catch
Sleep(10)
until success || A_TickCount > stopAt
} else {
; open a new explorer window with the path selected
; gotcha: the quotes must start after the comma!
Run("explorer.exe /select,`"" path "`"")
}
}
; get currently active explorer window or nothing
; https://autohotkey.com/board/topic/102127-navigating-explorer-directories/
GetActiveExplorer()
{
WinHWND := WinActive("A") ; Active window
for Item in ComObjCreate("Shell.Application").Windows
if (Item.HWND == WinHWND)
return Item ; Return active window object
}
; enumerate all locations of file
; prepends drive letter before the output paths to make them whole :)
; todo: take advantage of buflen being set to the required length
; https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilenamew
; https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextfilenamew
ListLinks(path)
{
static ERROR_MORE_DATA := 234
static MAX_PATH := 260
root := SubStr(path, 1, 2)
paths := []
buflen := MAX_PATH
VarSetStrCapacity(linkname, buflen)
handle := DllCall("FindFirstFileNameW",
"WStr", path,
"UInt", 0,
"UInt*", buflen,
"WStr", linkname)
if (A_LastError == ERROR_MORE_DATA)
throw "ListLinks: ERROR_MORE_DATA, 260 was not enough..."
if (handle == 0xffffffff)
throw "ListLinks: FindFirstFileNameW failed"
try
{
Loop
{
paths.Push(root linkname)
buflen := MAX_PATH
VarSetStrCapacity(linkname, buflen)
more := DllCall("FindNextFileNameW",
"UInt", handle,
"UInt*", buflen,
"WStr", linkname)
} until (!more)
if (A_LastError == ERROR_MORE_DATA)
throw "ListLinks: ERROR_MORE_DATA, 260 was not enough..."
} finally
DllCall("FindClose", "UInt", handle)
return paths
}
I use Link Shell Extension to see and create all of these links natively in Explorer. It's possible to see a list of locations that point to a file from Properties (from a tab added by LSE), but that's very annoying compared to how this script is used.
The script takes one command-line argument, a path to a file.
If the file has no hardlinks (is the only reference to a file), it does nothing.
If the file is hardlinked, it navigates the currently active Explorer window to one of the other locations, and opens new Explorer windows with the other locations highlighted.
To use this script, do anything that would cause the script to be run with the path as its argument.
- In cmd, run showlink.ahk [file path here]
- Drag and drop a file onto the script (if you have setup drag and drop onto .ahk files)
- Create a shortcut to the script and drag and drop a file onto it
My favorite way is to add a Send To link to it.
Go to shell:sendto (which is actually %appdata%\Microsoft\Windows\SendTo) and put a shortcut to this script there.
Now you can right-click any file and send it to this script to navigate to other locations of that file.
I also put &K in my shortcut's name so that I can write Menunk to rightclick a file, select Send to and select showlink.ahk (K).
Hardlinks are useful to me because among other uses, they create a two-way link between two or more locations, united by the underlying file they represent.
For example, I have video/audio files strewn about my disk, with annoying sidecar files. I linked the media files to a central location and moved the sidecars there; or rather, moved all the files to the central location and linked the media to where it's most convenient.
Thus, I am free to rename, move etc. the media file alone, with no clutter beside it and no need to rename many files at a time. I can follow the hardlink to see the sidecars, or go from the central location to the media regardless of where the media is, and furthermore always be able to recover the file if I happen to delete it, because it was never deleted, the file still remains in the central folder.
(One other way to do the above is to embed the sidecar files as Alternate Data Streams, but I'm irrationally scared of them)