0

I would like to extract some info from a .lnk file in Java, specifically the entire target (with command line parameters after the initial .exe) and the working directory as well.

In the question Windows shortcut (.lnk) parser in Java? by user Zarkonnen we can find the WindowsShortcut library created by multiple community users. See Code Blings answer here.

However, as of now, this library provides only access to the file path itself, but not to command line arguments or working directory (or any other additional info that might be inside a shortcut file).

I tried to figure out a way to get the additional info using the WindowsShortcut library, but didn't succeed. The library only provides me with a getRealFilename() method:

public static void main(String[] args) throws Exception
{
    WindowsShortcut windowsShortcut = new WindowsShortcut(new File("C:\test\test.lnk"));
    System.out.println(windowsShortcut.getRealFilename());
}

Does anyone know of a way to do this?

Screenshot of the properties of shortcut file with command line and working directory highlighted

Max Vollmer
  • 8,412
  • 9
  • 28
  • 43
fen
  • 73
  • 6
  • those libraries are what was proposed as an answer on this topic: https://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java – fen May 30 '18 at 00:12
  • ` File file = (File)list.get(0); System.out.println(file.getAbsolutePath()); System.out.println(file.getCanonicalPath()); System.out.println(file.getPath()); WindowsShortcut ws = new WindowsShortcut(file); System.out.println(ws.getRealFilename()); LnkParse lp = new LnkParse(); lp.parse(file.getPath()); System.out.println(lp.getFullPath()); System.out.println(lp.getLocalPath()); System.out.println(lp.getShareName());` – fen May 30 '18 at 00:16
  • I always get the anything.exe part but not the arguments after it. – fen May 30 '18 at 00:17
  • Thanks for your clarifications. However please don't provide essential information in comments. I think your question has potential to be a really good one, if you edit it with a reference to the [lnk parser question](https://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java) and a [MCVE] with desired and actual output. I will upvote once you've done that. – Max Vollmer May 30 '18 at 01:50
  • I would like to but I have nothing to show since I don't know how to use it. All I know is that there's no method in that class to retrieve the working directory and arguments. I've read everything I could find on google and while I found a few references to the working directory and some offsets contained in lnk files that I tried to use mimicking some of the devices that were used to parse other info in that class, nothing worked so far. I provided the class I used and a screencapture of the data I want to extract. I'm unsure any of my random attempts could help anyone answering that topic. – fen May 30 '18 at 19:02
  • 1
    I took the liberty of rewriting your question, because I really like the problem you were facing and think it's a valuable addition to StackOverflow. If you're unhappy with the changes, feel free to edit it again. – Max Vollmer Jun 02 '18 at 00:29
  • Thanks for your time and sorry I couldn't help more than that, I'll work on what you posted and I'll post again when I'm done. I should have mentionned that I've never worked with low level code (bytes manipulation and such) and I'm not familiar with those concepts, the 0x18 values and such didn't really look like what I found concerning the information I was interested in (longer values..?), and trying to use those instead didn't provide the expected Strings (All I ended up getting was a "ÑêʀĶ*}Ê€6±ð}Ê"). – fen Jun 02 '18 at 10:57
  • I was working on a drag and drop swing application meant to extract command line data from shortcuts to integrate them in a database, and use them from my application instead. Thanks again. – fen Jun 02 '18 at 10:58

1 Answers1

1

Your question is really good. As of the date of your question the WindowsShortcut class you refer to only implements code to get the path to the file pointed to by a shortcut, but doesn't provide any further data inside a shortcut file. But it's open source, so let's extend it!

Let's do some research first

In the inofficial documentation by Jesse Hager we find this:

 ______________________________________________________________________________
|                                                                              |
|  **The flags**                                                               |
|______________________________________________________________________________|
|     |                                    |                                   |
| Bit | Meaning when 1                     | Meaning when 0                    |
|_____|____________________________________|___________________________________|
|     |                                    |                                   |
|  0  | The shell item id list is present. | The shell item id list is absent. |
|  1  | Points to a file or directory.     | Points to something else.         |
|  2  | Has a description string.          | No description string.            |
|  3  | Has a relative path string.        | No relative path.                 |
|  4  | Has a working directory.           | No working directory.             |
|  5  | Has command line arguments.        | No command line arguments.        |
|  6  | Has a custom icon.                 | Has the default icon.             |
|_____|____________________________________|___________________________________|

So we know that we can check the flags byte for the existence of these additional strings. And we already have access to the flags byte prepared in our WindowsShortcut class.

Now we only need to know where those strings are stored in the shortcut file. In the inofficial documentation we also find this structure:

File header
Shell item ID list
    Item 1
    Item 2
    etc..
File locator info
    Local path
    Network path
Description string
Relative path string
Working directory string
Command line string
Icon filename string
Extra stuff

So the strings we are interested in come directly after the File locator info block. Which is neat, because the existing WindowsShortcut class already parses the File locator info to get the file path.

The docs also say that each string consists of a length given as unsigned short and then ASCII characters. However, at least under Windows10, I encountered UTF-16 strings and implemented my code accordingly.

Let's implement!

We can simply add a few more lines at the end of the parseLink method.

First we get the offset directly after the File locator info block and call it next_string_start, as it now points to the first additional string:

final int file_location_size = bytesToDword(link, file_start);
int next_string_start = file_start + file_location_size;

We then check the flags for each of the strings in order, and if it exists, we parse it:

final byte has_description             = (byte)0b00000100;
final byte has_relative_path           = (byte)0b00001000;
final byte has_working_directory       = (byte)0b00010000;
final byte has_command_line_arguments  = (byte)0b00100000;

// if description is present, parse it
if ((flags & has_description) > 0) {
    final int string_len = bytesToWord(link, next_string_start) * 2; // times 2 because UTF-16
    description = getUTF16String(link, next_string_start + 2, string_len);
    next_string_start = next_string_start + string_len + 2;
}

// if relative path is present, parse it
if ((flags & has_relative_path) > 0) {
    final int string_len = bytesToWord(link, next_string_start) * 2; // times 2 because UTF-16
    relative_path = getUTF16String(link, next_string_start + 2, string_len);
    next_string_start = next_string_start + string_len + 2;
}

// if working directory is present, parse it
if ((flags & has_working_directory) > 0) {
    final int string_len = bytesToWord(link, next_string_start) * 2; // times 2 because UTF-16
    working_directory = getUTF16String(link, next_string_start + 2, string_len);
    next_string_start = next_string_start + string_len + 2;
}

// if command line arguments are present, parse them
if ((flags & has_command_line_arguments) > 0) {
    final int string_len = bytesToWord(link, next_string_start) * 2; // times 2 because UTF-16
    command_line_arguments = getUTF16String(link, next_string_start + 2, string_len);
    next_string_start = next_string_start + string_len + 2;
}

The getUTF16String method is simply:

private static String getUTF16String(final byte[] bytes, final int off, final int len) {
    return new String(bytes, off, len, StandardCharsets.UTF_16LE);
}

And finally we need members and getters for those new Strings:

private String description;
private String relative_path;
private String working_directory;
private String command_line_arguments;

public String getDescription() {
    return description;
}

public String getRelativePath() {
    return relative_path;
}

public String getWorkingDirectory() {
    return working_directory;
}

public String getCommandLineArguments() {
    return command_line_arguments;
}

I tested this under Windows 10 and it worked like a charm.

I made a pull request with my changes on the original repo, until then you can also find the complete code here.

Max Vollmer
  • 8,412
  • 9
  • 28
  • 43