Reading shortcut files -- Parsing .lnk and .url files

Got a LiveCode personal license? Are you a beginner, hobbyist or educator that's new to LiveCode? This forum is the place to go for help getting started. Welcome!

Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller

Post Reply
golife
Posts: 103
Joined: Fri Apr 02, 2010 12:10 pm

Reading shortcut files -- Parsing .lnk and .url files

Post by golife » Sat Feb 27, 2021 11:01 am

Since on my Windows laptop there were thousands of shortcut files, Internet shortcuts and local shortcuts, I decided to collect them all in one folder and store them in a database (or you can store as a text file).

For Macintosh: File formats are different. Hagan posted a note (Feb 26, 2021) on the user list (use-livecode@lists.runrev.com) that may be used for that platform. Additional comments for better and other solutions are welcome, of course. I hope it is ok to repost Hagan's comment here:

On macOS URL link files are stored as a plist-file like:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>URL</key>
<string>https://lessons.livecode.com/</string>
</dict>
</plist>


On Windows, these shortcut files do not display a file extension even if extensions are made to show up. The hidden extensions used are ".lnk" or ".url" .But they will show up using the context menu (right mouse button) and checking the menu item "Properties". Also they become visible when calling the LC function files().

Local shortcuts ".lnk" files point to either files or folders or application icons and can start a process/app or open the target folder.
URL files are mostly generated when the user drags-and-drops the Internet Browser's URL field to the desktop.


Since Shortcut files with the ".lnk" extension have a binary format on Windows, the correct way to read out the file path to the target file (the file or folder the shortcut is pointing to) would be to actually read the binary format using binaryencode/binarydecode functions and reading chunks of bytes to detect the size of the target path string and the byte offset. I did not have that time and need to do so and looked at the file content to find clues that allow to extract the target link as a complete file path. And I am not fluent with bit and byte operations.

Therefore, this here is a hack that works for me and I did not detect any problem yet. If you have a similar need, you will have to adjust the function hackLnkFile() using your computer name and your drive letter on Windows by redefining two local variables.

The code is commented in detail so that also beginners may understand how to write a little hack in LiveCode. The script uses LC functions such as files(), merge(), URL("binfile:"&<filepath>) and offset().

I suggest to put all code/script into one script only unless for certain other reasons. It is by far easier to have all handlers (=commands or functions) in one script for development and debugging. Here I used the card script exclusively. There are no scripts in buttons, fields or any other object. All is put into one script package (which could also be a script of a behavior). The names of buttons or fields trigger the switch statement to execute.

I am using explicit variable declarations always for valid reasons and I am using LC naming conventions.
Additionally:
"-->" is a comment mark pointing to a handler and it's location
"--x" is a comment mark for a statement used for debugging which later will be removed
" put ... into msg --x" The message box is only used in development. Again, it is explicitly named to not confuse it with other 'put' statements.
To differentiate internal LC command and message handlers -- for example 'on mouseUp' -- from user defined commands, I am always using the synonymous term 'command myCommandName' when creating a command/message handler, as is also recommended.

Code: Select all

## Script author: Golive
## Script location: Card script
## Stack name: rhFileParser-url-lnk.livecode
## Last update: 2021-02-25 22:35

on mouseUp
   local tTarget
   put the short name of the target into tTarget
   switch tTarget
      case "getFolder" -- Button with name "getFolder"
         getFolder
         break
      case "getFiles" -- Button with name "getFiles"
         getFiles
         break
      case "getLinkFiles" -- Button with name "getLinkFiles"
         getLinkFiles
         break
   end switch
end mouseUp

command getLinkFiles
   lock screen
   put getLinkFiles(field "folderpath") into field "list" -- Local field "list"
   unlock screen
end getLinkFiles

command getFiles pFolderPath
   if pFolderPath is empty then put field "folderpath" into pFolderPath
   if there is not a folder pFolderPath then exit to top
   put files (pFolderPath) into field "list" --> LC function
end getFiles

command getFolder
   local tFolderPath
   answer folder "Please select the folder path"
   if it is empty then exit to top
   put it into tFolderPath
   put tFolderPath into field "folderPath" -- Local field
end getFolder

function getLinkFiles pFolderPath
   ## On Windows: Reads the URL or Link of Windows shortcut files with the invisible extension ".lnk" or ".url"
   # pFolderPath: Path to the folder that contains 'link' files
   # Returns records of 'link' files for a given folder separated by a line containing three dashes/hyphens "---"
   
   local tFileNames -- List of file names in a folder
   local tFileName -- Single file name
   local tFilePath --The path  to a specific file
   local tRecordList -- The file list containing records with target file paths 
   local tContent -- Content of a 'link' or shortcut file (binary for ".ink" files
   
   // Validation
   if pFolderPath is empty then
      if there is a field "folderPath" then -- Local field
         then put field "folderpath" into pFolderPath
      end if
   end if
   if there is not a folder pFolderPath then return "Error: Source Folder does not exist."
   
   put files (pFolderPath) into tFileNames --> LC function
   if tFileNames is empty then return "Error: Source Folder contains no files."
   
   // Go through each filename
   repeat with i = 1 to the number of lines of tFileNames
      set the cursor to busy
      put line i of tFileNames into tFileName
      put pFolderPath &"/"&tFileName into tFilePath
      
      // Skip files that are not looked at here
      // Could be omitted if using filter functions before to filter the requested file types
      if ".url" is not in tFileName AND ".lnk" is not in tFileName then
         next repeat
      end if
      
      // Get the content of the source link file
      put URL("binfile:"&tFilePath) into tContent
      
      // Build a record of information associated with the link file and target file/folder
      // The format is the same as the standard format of ".url." files
      if ".url" is in tFilename then 
         put word 1 to -1 of tFilename &CR& pFolderPath &CR& word 1 to -1 of tContent  &CR&"---"&CR after tRecordList
      else if ".lnk" is in tFileName then
         put hackLnkFile(tContent) into tContent --> Same script function parsing the binary file format of ".lnk" files
         put word 1 to -1 of  tFilename &CR& pFolderPath &CR& "[LocalLink]" &CR& word 1 to -1 of tContent  &CR&"---"&CR after tRecordList
      end if
   end repeat
   
   delete last line of tRecordList
   return "---"&CR& tRecordList 
   
end getLinkFiles

function hackLnkFile pData
   ## On Windows: Hack the binary ".lnk" file format with quick 'dirty' solution.
   # The Windows file mainly contains binary data and we want to extract the target filepath.
   # It would be correct to actually read the relevant bytes from the binary format of ".lnk" files.
   # We are not only extracing target file paths, but also target folder paths.
   # Returns a filepath string starting from the local system the drive name indicating the target file or folder.
   
   local tComputerName = "xAcer" -- Use your computer name with an "x" in front. This here is my computer name.
   local tDriveLetter = "C:\" -- Use the current drive letter
   local tNewContent -- Collecting data in the tNewContent string
   local tExtList -- Item list of file extension you may look for
   local tDelimiter -- A string that seems to delimit the file path extracted
   local tChar -- Local char to process
   local a,b -- start and ending integers to extract the filepath from the content
   
   // Filtering out valid file name characters
   // There are better way using chartonum() checks or filter with REGEX functions.
   repeat for each char tChar in pData
      // Add whatever characters are used in your language and used to name files
      if tChar is in "abcedfghijklmnopqrstuvwsxyzüöäéèà*@|+1234567890 .:\/-[]()_" then put tChar after tNewContent
   end repeat
   
   // Use file extensions you are interested in:
   put merge(".txt,.pdf,.livecode,.rev,.docx,.xlsx,.png,.pre,[[tComputerName]]") into tExtList
   
   // Get the file extension as a delimiter for each file
   repeat for each item tItem in tExtList
      if tItem is in tNewContent then
         put tItem into tDelimiter
         exit repeat
      end if
   end repeat
   
   // Get the first part of the target file path
   put offset(tDriveLetter,tNewContent) into a
   put char a to -1 of tNewContent into tNewContent
   
   // Find the end of the target file path
   // The logic is from trial and error analyzing the ".lnk" file of Windwos
   if tDelimiter is in tNewContent and tDelimiter is not empty then
      if tComputerName is tDelimiter then
         put offset (tDelimiter,tNewContent) -1   into b
      else
         put offset (tDelimiter,tNewContent) + length(tDelimiter) -1  into b
      end if
   else if ".." is in tDelimiter then 
      put b - length(tDelimiter)-1 into b
   else
      put -1 into b -- If nothing else works, get the undelimited string
   end if
   
   // Return the filepath with "URL =" as the prefix to get the same format as in ".url" files
   return "URL ="& char 1 to b of tNewContent
end hackLnkFile

P.S. Someone interested to actually parse the binary format and get a function not being a hack, Mathias posted a link to such a discussion: https://stackoverflow.com/questions/397 ... -in-python.

Also, here is a detailed file description published by Microsoft: https://docs.microsoft.com/en-us/opensp ... 1d6cc0f943

Post Reply

Return to “Getting Started with LiveCode - Complete Beginners”