【问题标题】:Windows shortcut (.lnk) parser in Java?Java中的Windows快捷方式(.lnk)解析器?
【发布时间】:2010-09-23 11:53:52
【问题描述】:

我目前正在使用Win32ShellFolderManager2ShellFolder.getLinkLocation 来解析Java 中的Windows 快捷方式。不幸的是,如果 Java 程序在 Vista 下作为服务运行,getLinkLocation,这不起作用。具体来说,我收到一个异常说明“无法获取 shell 文件夹 ID 列表”。

搜索网络确实会提到此错误消息,但总是与JFileChooser 有关。我没有使用JFileChooser,我只需要将.lnk 文件解析到它的目的地。

有人知道我可以使用用 Java 编写的 .lnk 文件的第 3 方解析器吗?

我后来找到了 .lnk 格式 here 的非官方文档,但如果有人以前做过,我宁愿不必做这项工作,因为这种格式相当可怕。

【问题讨论】:

    标签: java windows windows-vista shortcuts lnk


    【解决方案1】:

    添加了 cmets(一些解释以及到目前为止每个贡献者的功劳),对文件魔术的额外检查,快速测试以查看给定文件是否可能是有效链接(无需读取所有字节),a如果文件太小,则修复抛出带有适当消息而不是 ArrayIndexOutOfBoundsException 的 ParseException,进行了一些常规清理。

    来源here(如果您有任何更改,请将它们直接推送到GitHub repo/project

    package org.stackoverflowusers.file;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.text.ParseException;
    
    /**
     * Represents a Windows shortcut (typically visible to Java only as a '.lnk' file).
     *
     * Retrieved 2011-09-23 from http://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java/672775#672775
     * Originally called LnkParser
     *
     * Written by: (the stack overflow users, obviously!)
     *   Apache Commons VFS dependency removed by crysxd (why were we using that!?) https://github.com/crysxd
     *   Headerified, refactored and commented by Code Bling http://stackoverflow.com/users/675721/code-bling
     *   Network file support added by Stefan Cordes http://stackoverflow.com/users/81330/stefan-cordes
     *   Adapted by Sam Brightman http://stackoverflow.com/users/2492/sam-brightman
     *   Based on information in 'The Windows Shortcut File Format' by Jesse Hager <jessehager@iname.com>
     *   And somewhat based on code from the book 'Swing Hacks: Tips and Tools for Killer GUIs'
     *     by Joshua Marinacci and Chris Adamson
     *     ISBN: 0-596-00907-0
     *     http://www.oreilly.com/catalog/swinghks/
     */
    public class WindowsShortcut
    {
        private boolean isDirectory;
        private boolean isLocal;
        private String real_file;
    
        /**
         * Provides a quick test to see if this could be a valid link !
         * If you try to instantiate a new WindowShortcut and the link is not valid,
         * Exceptions may be thrown and Exceptions are extremely slow to generate,
         * therefore any code needing to loop through several files should first check this.
         *
         * @param file the potential link
         * @return true if may be a link, false otherwise
         * @throws IOException if an IOException is thrown while reading from the file
         */
        public static boolean isPotentialValidLink(File file) throws IOException {
            final int minimum_length = 0x64;
            InputStream fis = new FileInputStream(file);
            boolean isPotentiallyValid = false;
            try {
                isPotentiallyValid = file.isFile()
                    && file.getName().toLowerCase().endsWith(".lnk")
                    && fis.available() >= minimum_length
                    && isMagicPresent(getBytes(fis, 32));
            } finally {
                fis.close();
            }
            return isPotentiallyValid;
        }
    
        public WindowsShortcut(File file) throws IOException, ParseException {
            InputStream in = new FileInputStream(file);
            try {
                parseLink(getBytes(in));
            } finally {
                in.close();
            }
        }
    
        /**
         * @return the name of the filesystem object pointed to by this shortcut
         */
        public String getRealFilename() {
            return real_file;
        }
    
        /**
         * Tests if the shortcut points to a local resource.
         * @return true if the 'local' bit is set in this shortcut, false otherwise
         */
        public boolean isLocal() {
            return isLocal;
        }
    
        /**
         * Tests if the shortcut points to a directory.
         * @return true if the 'directory' bit is set in this shortcut, false otherwise
         */
        public boolean isDirectory() {
            return isDirectory;
        }
    
        /**
         * Gets all the bytes from an InputStream
         * @param in the InputStream from which to read bytes
         * @return array of all the bytes contained in 'in'
         * @throws IOException if an IOException is encountered while reading the data from the InputStream
         */
        private static byte[] getBytes(InputStream in) throws IOException {
            return getBytes(in, null);
        }
        
        /**
         * Gets up to max bytes from an InputStream
         * @param in the InputStream from which to read bytes
         * @param max maximum number of bytes to read
         * @return array of all the bytes contained in 'in'
         * @throws IOException if an IOException is encountered while reading the data from the InputStream
         */
        private static byte[] getBytes(InputStream in, Integer max) throws IOException {
            // read the entire file into a byte buffer
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            byte[] buff = new byte[256];
            while (max == null || max > 0) {
                int n = in.read(buff);
                if (n == -1) {
                    break;
                }
                bout.write(buff, 0, n);
                if (max != null)
                    max -= n;
            }
            in.close();
            return bout.toByteArray();
        }
    
        private static boolean isMagicPresent(byte[] link) {
            final int magic = 0x0000004C;
            final int magic_offset = 0x00;
            return link.length >= 32 && bytesToDword(link, magic_offset) == magic;
        }
    
        /**
         * Gobbles up link data by parsing it and storing info in member fields
         * @param link all the bytes from the .lnk file
         */
        private void parseLink(byte[] link) throws ParseException {
            try {
                if (!isMagicPresent(link))
                    throw new ParseException("Invalid shortcut; magic is missing", 0);
    
                // get the flags byte
                byte flags = link[0x14];
    
                // get the file attributes byte
                final int file_atts_offset = 0x18;
                byte file_atts = link[file_atts_offset];
                byte is_dir_mask = (byte)0x10;
                if ((file_atts & is_dir_mask) > 0) {
                    isDirectory = true;
                } else {
                    isDirectory = false;
                }
    
                // if the shell settings are present, skip them
                final int shell_offset = 0x4c;
                final byte has_shell_mask = (byte)0x01;
                int shell_len = 0;
                if ((flags & has_shell_mask) > 0) {
                    // the plus 2 accounts for the length marker itself
                    shell_len = bytesToWord(link, shell_offset) + 2;
                }
    
                // get to the file settings
                int file_start = 0x4c + shell_len;
    
                final int file_location_info_flag_offset_offset = 0x08;
                int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset];
                isLocal = (file_location_info_flag & 2) == 0;
                // get the local volume and local system values
                //final int localVolumeTable_offset_offset = 0x0C;
                final int basename_offset_offset = 0x10;
                final int networkVolumeTable_offset_offset = 0x14;
                final int finalname_offset_offset = 0x18;
                int finalname_offset = link[file_start + finalname_offset_offset] + file_start;
                String finalname = getNullDelimitedString(link, finalname_offset);
                if (isLocal) {
                    int basename_offset = link[file_start + basename_offset_offset] + file_start;
                    String basename = getNullDelimitedString(link, basename_offset);
                    real_file = basename + finalname;
                } else {
                    int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start;
                    int shareName_offset_offset = 0x08;
                    int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset]
                        + networkVolumeTable_offset;
                    String shareName = getNullDelimitedString(link, shareName_offset);
                    real_file = shareName + "\\" + finalname;
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                throw new ParseException("Could not be parsed, probably not a valid WindowsShortcut", 0);
            }
        }
    
        private static String getNullDelimitedString(byte[] bytes, int off) {
            int len = 0;
            // count bytes until the null character (0)
            while (true) {
                if (bytes[off + len] == 0) {
                    break;
                }
                len++;
            }
            return new String(bytes, off, len);
        }
    
        /*
         * convert two bytes into a short note, this is little endian because it's
         * for an Intel only OS.
         */
        private static int bytesToWord(byte[] bytes, int off) {
            return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
        }
    
        private static int bytesToDword(byte[] bytes, int off) {
            return (bytesToWord(bytes, off + 2) << 16) | bytesToWord(bytes, off);
        }
    
    }
    

    【讨论】:

    • 已经用了几个月了,觉得应该分享一下。
    • 创建快捷方式怎么样?还有图标?
    • @TomášZato 加入进来真的很棒
    • @TomášZato 你最后有没有找到任何东西来创建快捷方式,或者修改图标?如果有,请分享!
    【解决方案2】:

    Sam Brightman's solution 仅用于本地文件。 我添加了对网络文件的支持:

    • Windows shortcut (.lnk) parser in Java?
    • http://code.google.com/p/8bits/downloads/detail?name=The_Windows_Shortcut_File_Format.pdf
    • http://www.javafaq.nu/java-example-code-468.html

      public class LnkParser {
      
      public LnkParser(File f) throws IOException {
          parse(f);
      }
      
      private boolean isDirectory;
      private boolean isLocal;
      
      public boolean isDirectory() {
          return isDirectory;
      }
      
      private String real_file;
      
      public String getRealFilename() {
          return real_file;
      }
      
      private void parse(File f) throws IOException {
          // read the entire file into a byte buffer
          FileInputStream fin = new FileInputStream(f);
          ByteArrayOutputStream bout = new ByteArrayOutputStream();
          byte[] buff = new byte[256];
          while (true) {
              int n = fin.read(buff);
              if (n == -1) {
                  break;
              }
              bout.write(buff, 0, n);
          }
          fin.close();
          byte[] link = bout.toByteArray();
      
          parseLink(link);
      }
      
      private void parseLink(byte[] link) {
          // get the flags byte
          byte flags = link[0x14];
      
          // get the file attributes byte
          final int file_atts_offset = 0x18;
          byte file_atts = link[file_atts_offset];
          byte is_dir_mask = (byte)0x10;
          if ((file_atts & is_dir_mask) > 0) {
              isDirectory = true;
          } else {
              isDirectory = false;
          }
      
          // if the shell settings are present, skip them
          final int shell_offset = 0x4c;
          final byte has_shell_mask = (byte)0x01;
          int shell_len = 0;
          if ((flags & has_shell_mask) > 0) {
              // the plus 2 accounts for the length marker itself
              shell_len = bytes2short(link, shell_offset) + 2;
          }
      
          // get to the file settings
          int file_start = 0x4c + shell_len;
      
          final int file_location_info_flag_offset_offset = 0x08;
          int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset];
          isLocal = (file_location_info_flag & 2) == 0;
          // get the local volume and local system values
          //final int localVolumeTable_offset_offset = 0x0C;
          final int basename_offset_offset = 0x10;
          final int networkVolumeTable_offset_offset = 0x14;
          final int finalname_offset_offset = 0x18;
          int finalname_offset = link[file_start + finalname_offset_offset] + file_start;
          String finalname = getNullDelimitedString(link, finalname_offset);
          if (isLocal) {
              int basename_offset = link[file_start + basename_offset_offset] + file_start;
              String basename = getNullDelimitedString(link, basename_offset);
              real_file = basename + finalname;
          } else {
              int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start;
              int shareName_offset_offset = 0x08;
              int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset]
                      + networkVolumeTable_offset;
              String shareName = getNullDelimitedString(link, shareName_offset);
              real_file = shareName + "\\" + finalname;
          }
      }
      
      private static String getNullDelimitedString(byte[] bytes, int off) {
          int len = 0;
          // count bytes until the null character (0)
          while (true) {
              if (bytes[off + len] == 0) {
                  break;
              }
              len++;
          }
          return new String(bytes, off, len);
      }
      
      /*
       * convert two bytes into a short note, this is little endian because it's
       * for an Intel only OS.
       */
      private static int bytes2short(byte[] bytes, int off) {
          return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
      }
      
      /**
       * Returns the value of the instance variable 'isLocal'.
       *
       * @return Returns the isLocal.
       */
      public boolean isLocal() {
          return isLocal;
      }
      }
      

    【讨论】:

    • 感谢代码,我把它做成了一个静态方法,将链接路径作为字符串并将目标文件的路径作为字符串返回。
    【解决方案3】:

    我可以在 GitHub 上推荐这个存储库:

    https://github.com/BlackOverlord666/mslinks

    在那里,我找到了 create 快捷方式的简单解决方案:

    ShellLink.createLink("path/to/existing/file.txt", "path/to/the/future/shortcut.lnk");

    如果你想阅读快捷键:

    File shortcut = ...;
    String pathToExistingFile = new ShellLink(shortcut).resolveTarget();
    

    如果您想更改快捷方式的图标,请使用:

    ShellLink sl = ...;
    sl.setIconLocation("/path/to/icon/file");
    

    您可以编辑快捷链接的大多数属性,例如工作目录、工具提示文本、图标、命令行参数、热键、创建 LAN 共享文件和目录的链接等等...

    希望这对你有帮助:)

    亲切的问候 约书亚·弗兰克

    【讨论】:

    • 这太完美了!!
    【解决方案4】:

    链接到的代码 plan9assembler 似乎只需稍作修改即可工作。我认为当bytes 在bytes2short 函数中向上转换为ints 时,需要更改的只是“&amp; 0xff”来防止符号扩展。我添加了http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf 中描述的功能来连接“路径名的最后部分”,即使在实践中这似乎没有在我的示例中使用。我没有在标题中添加任何错误检查或处理网络共享。这是我现在使用的:

    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.text.DecimalFormat;
    import java.text.NumberFormat;
    
    public class LnkParser {
    
        public LnkParser(File f) throws Exception {
            parse(f);
        }
    
        private boolean is_dir;
    
        public boolean isDirectory() {
            return is_dir;
        }
    
        private String real_file;
    
        public String getRealFilename() {
            return real_file;
        }
    
        private void parse(File f) throws Exception {
            // read the entire file into a byte buffer
            FileInputStream fin = new FileInputStream(f);
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            byte[] buff = new byte[256];
            while (true) {
                int n = fin.read(buff);
                if (n == -1) {
                    break;
                }
                bout.write(buff, 0, n);
            }
            fin.close();
            byte[] link = bout.toByteArray();
    
            // get the flags byte
            byte flags = link[0x14];
    
            // get the file attributes byte
            final int file_atts_offset = 0x18;
            byte file_atts = link[file_atts_offset];
            byte is_dir_mask = (byte) 0x10;
            if ((file_atts & is_dir_mask) > 0) {
                is_dir = true;
            } else {
                is_dir = false;
            }
    
            // if the shell settings are present, skip them
            final int shell_offset = 0x4c;
            final byte has_shell_mask = (byte) 0x01;
            int shell_len = 0;
            if ((flags & has_shell_mask) > 0) {
                // the plus 2 accounts for the length marker itself
                shell_len = bytes2short(link, shell_offset) + 2;
            }
    
            // get to the file settings
            int file_start = 0x4c + shell_len;
    
            // get the local volume and local system values
            final int basename_offset_offset = 0x10;
            final int finalname_offset_offset = 0x18;
            int basename_offset = link[file_start + basename_offset_offset]
                                    + file_start;
            int finalname_offset = link[file_start + finalname_offset_offset]
                                    + file_start;
            String basename = getNullDelimitedString(link, basename_offset);
            String finalname = getNullDelimitedString(link, finalname_offset);
            real_file = basename + finalname;
        }
    
        private static String getNullDelimitedString(byte[] bytes, int off) {
            int len = 0;
            // count bytes until the null character (0)
            while (true) {
                if (bytes[off + len] == 0) {
                    break;
                }
                len++;
            }
            return new String(bytes, off, len);
        }
    
        /*
         * convert two bytes into a short note, this is little endian because it's
         * for an Intel only OS.
         */
        private static int bytes2short(byte[] bytes, int off) {
            return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
        }
    }
    

    【讨论】:

      【解决方案5】:

      我也曾在 Java 中的“.lnk”上工作过(现在没有时间)。我的代码是here

      有点乱(一些测试垃圾),但本地和网络解析效果很好。创建链接也实现了。请测试并将补丁发送给我。

      解析示例:

      Shortcut scut = Shortcut.loadShortcut(new File("C:\\t.lnk"));
      System.out.println(scut.toString());
      

      创建新链接:

      Shortcut scut = new Shortcut(new File("C:\\temp"));
      OutputStream os = new FileOutputStream("C:\\t.lnk");
      os.write(scut.getBytes());
      os.flush();
      os.close();
      

      【讨论】:

        【解决方案6】:

        @Code Bling 的解决方案不适用于 User 目录中的 Files。
        例如“C:/Users/Username/Filename.txt”。
        原因是:在The_Windows_Shortcut_File_Format.pdf

        @Stefan Cordes 在第 6 页提到,它说只有前 2 位对卷信息很重要。 当volumes info的第一位为“0”时,所有其他位可能被随机垃圾填充。

        如果涉及到:

        isLocal = (file_location_info_flag & 2) == 0;
        

        那么file_location_info_flag 可能是“3”。 该文件仍然是本地文件,但是这行代码将false 分配给isLocal

        所以我建议对@Code Bling 的代码进行以下调整:

        isLocal = (file_location_info_flag & 1) == 1;
        

        【讨论】:

        • 我从未收到过这方面的通知,因为@User 在答案中不起作用。我明白你的意思。原始代码假定文件是本地文件或网络文件(不是两者),但似乎它们并不相互排斥。您是否碰巧有此示例.lnk
        • 我已经做出改变了
        【解决方案7】:

        这个短代码真的很有用...

        但需要两个修复:

        • isPotentialValidLink 改进后,如果名称不以“.lnk”结尾,则不加载文件

            public static boolean isPotentialValidLink(final File file) {
              final int minimum_length = 0x64;
              boolean isPotentiallyValid = false;
              if (file.getName().toLowerCase().endsWith(".lnk"))
                  try (final InputStream fis = new FileInputStream(file)) {
                      isPotentiallyValid = file.isFile() && fis.available() >= minimum_length && isMagicPresent(getBytes(fis, 32));
                  } catch (Exception e) {
                      // forget it
                  }
              return isPotentiallyValid;
            }
          
        • 偏移量必须用 32 位计算,而不仅仅是一个字节...

           final int finalname_offset = bytesToDword(link,file_start + finalname_offset_offset) + file_start;
           final int basename_offset = bytesToDword(link,file_start + basename_offset_offset) + file_start;
          

        【讨论】:

        • 如果你在 Github 上提交 pull request,我会合并你的更改,否则我有时间再做!
        • 我刚刚合并了 bytesToDword 更改。我没有注意到它们不在原始 PR 中。
        【解决方案8】:

        我发现了其他非专业技术。完成我的工作。

        File file=new File("C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\TeamViewer.lnk");//shortcut link
                    FileInputStream stream=new FileInputStream(file);
                    DataInputStream st=new DataInputStream(stream);
                   byte[] bytes=new byte[(int)stream.getChannel().size()];
                   stream.read(bytes);
                   String data=new String(bytes);
                   int i1=data.indexOf("C:\\Program Files");
                   int i2=data.indexOf(".exe",i1);
                   System.out.println(data.substring(i1, i2+4));
        

        【讨论】:

          【解决方案9】:

          给定的代码运行良好,但有一个错误。 Java 字节是从 -128 到 127 的有符号值。我们想要一个从 0 到 255 的无符号值来获得正确的结果。只需将 bytes2short 函数更改如下:

          static int bytes2short(byte[] bytes, int off) {
              int low = (bytes[off]<0 ? bytes[off]+256 : bytes[off]);
              int high = (bytes[off+1]<0 ? bytes[off+1]+256 : bytes[off+1])<<8;
              return 0 | low | high;
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-05-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-25
            • 2013-11-08
            相关资源
            最近更新 更多