Reading the target of a .lnk file in Python?

前端 未结 7 2050
悲&欢浪女
悲&欢浪女 2020-11-28 04:12

I\'m trying to read the target file/directory of a shortcut (.lnk) file from Python. Is there a headache-free way to do it? The .lnk spec [PDF] is way over my h

相关标签:
7条回答
  • 2020-11-28 04:47

    Alternatively, you could try using SHGetFolderPath(). The following code might work, but I'm not on a Windows machine right now so I can't test it.

    import ctypes
    
    shell32 = ctypes.windll.shell32
    
    # allocate MAX_PATH bytes in buffer
    video_folder_path = ctypes.create_string_buffer(260)
    
    # 0xE is CSIDL_MYVIDEO
    # 0 is SHGFP_TYPE_CURRENT
    # If you want a Unicode path, use SHGetFolderPathW instead
    if shell32.SHGetFolderPathA(None, 0xE, None, 0, video_folder_path) >= 0:
        # success, video_folder_path now contains the correct path
    else:
        # error
    
    0 讨论(0)
  • 2020-11-28 04:51

    Create a shortcut using Python (via WSH)

    import sys
    import win32com.client 
    
    shell = win32com.client.Dispatch("WScript.Shell")
    shortcut = shell.CreateShortCut("t:\\test.lnk")
    shortcut.Targetpath = "t:\\ftemp"
    shortcut.save()
    


    Read the Target of a Shortcut using Python (via WSH)

    import sys
    import win32com.client 
    
    shell = win32com.client.Dispatch("WScript.Shell")
    shortcut = shell.CreateShortCut("t:\\test.lnk")
    print(shortcut.Targetpath)
    
    0 讨论(0)
  • 2020-11-28 04:56

    I also realize this question is old, but I found the answers to be most relevant to my situation.

    Like Jared's answer, I also could not use the win32com module. So Jared's use of the binary structure from MS-SHLLINK got me part of the way there. I needed read shortcuts on both Windows and Linux, where the shortcuts are created on a samba share by Windows. Jared's implementation didn't quite work for me, I think only because I encountered some different variations on the shortcut format. But, it gave me the start I needed (thanks Jared).

    So, here is a class named MSShortcut which expands on Jared's approach. However, the implementation is only Python3.4 and above, due to using some pathlib features added in Python3.4

    #!/usr/bin/python3
    
    # Link Format from MS: https://msdn.microsoft.com/en-us/library/dd871305.aspx
    # Need to be able to read shortcut target from .lnk file on linux or windows.
    # Original inspiration from: http://stackoverflow.com/questions/397125/reading-the-target-of-a-lnk-file-in-python
    
    from pathlib import Path, PureWindowsPath
    import struct, sys, warnings
    
    if sys.hexversion < 0x03040000:
        warnings.warn("'{}' module requires python3.4 version or above".format(__file__), ImportWarning)
    
    
    # doc says class id = 
    #    00021401-0000-0000-C000-000000000046
    # requiredCLSID = b'\x00\x02\x14\x01\x00\x00\x00\x00\xC0\x00\x00\x00\x00\x00\x00\x46'
    # Actually Getting: 
    requiredCLSID   = b'\x01\x14\x02\x00\x00\x00\x00\x00\xC0\x00\x00\x00\x00\x00\x00\x46'  # puzzling
    
    class ShortCutError(RuntimeError):
        pass
    
    
    class MSShortcut():
        """
        interface to Microsoft Shortcut Objects.  Purpose:
        - I need to be able to get the target from a samba shared on a linux machine
        - Also need to get access from a Windows machine.
        - Need to support several forms of the shortcut, as they seem be created differently depending on the 
          creating machine.
        - Included some 'flag' types in external interface to help test differences in shortcut types
    
        Args:
            scPath (str): path to shortcut
    
        Limitations:
            - There are some omitted object properties in the implementation. 
              Only implemented / tested enough to recover the shortcut target information. Recognized omissions:
              - LinkTargetIDList
              - VolumeId structure (if captured later, should be a separate class object to hold info)
              - Only captured environment block from extra data 
            - I don't know how or when some of the shortcut information is used, only captured what I recognized, 
              so there may be bugs related to use of the information
            - NO shortcut update support (though might be nice)
            - Implementation requires python 3.4 or greater
            - Tested only with Unicode data on a 64bit little endian machine, did not consider potential endian issues
    
        Not Debugged:
            - localBasePath - didn't check if parsed correctly or not.
            - commonPathSuffix
            - commonNetworkRelativeLink
    
        """
    
        def __init__(self, scPath):
            """
            Parse and keep shortcut properties on creation
            """
            self.scPath = Path(scPath)
    
            self._clsid = None
            self._lnkFlags = None
            self._lnkInfoFlags = None
            self._localBasePath = None
            self._commonPathSuffix = None
            self._commonNetworkRelativeLink = None
            self._name = None
            self._relativePath = None
            self._workingDir = None
            self._commandLineArgs = None
            self._iconLocation = None
            self._envTarget = None
    
            self._ParseLnkFile(self.scPath)  
    
    
        @property
        def clsid(self): 
            return self._clsid
    
        @property
        def lnkFlags(self): 
            return self._lnkFlags
    
        @property
        def lnkInfoFlags(self): 
            return self._lnkInfoFlags
    
        @property
        def localBasePath(self): 
            return self._localBasePath
    
        @property
        def commonPathSuffix(self): 
            return self._commonPathSuffix
    
        @property
        def commonNetworkRelativeLink(self): 
            return self._commonNetworkRelativeLink
    
        @property
        def name(self): 
            return self._name    
    
        @property
        def relativePath(self): 
            return self._relativePath    
    
        @property
        def workingDir(self): 
            return self._workingDir    
    
        @property
        def commandLineArgs(self): 
            return self._commandLineArgs    
    
        @property
        def iconLocation(self): 
            return self._iconLocation    
    
        @property
        def envTarget(self): 
            return self._envTarget    
    
    
        @property
        def targetPath(self):
            """
            Args:
                woAnchor (bool): remove the anchor (\\server\path or drive:) from returned path.
    
            Returns:
                a libpath PureWindowsPath object for combined workingDir/relative path
                or the envTarget
    
            Raises:
                ShortCutError when no target path found in Shortcut
            """
            target = None
            if self.workingDir:
                target = PureWindowsPath(self.workingDir)
                if self.relativePath:
                    target = target / PureWindowsPath(self.relativePath)
                else: target = None
    
            if not target and self.envTarget:
                target = PureWindowsPath(self.envTarget)
    
            if not target:
                raise ShortCutError("Unable to retrieve target path from MS Shortcut: shortcut = {}"
                                   .format(str(self.scPath)))  
    
            return target    
    
        @property
        def targetPathWOAnchor(self):
            tp = self.targetPath
            return tp.relative_to(tp.anchor)
    
    
    
    
        def _ParseLnkFile(self, lnkPath):        
            with lnkPath.open('rb') as f:
                content = f.read()
    
                # verify size  (4 bytes)
                hdrSize = struct.unpack('I', content[0x00:0x04])[0]
                if hdrSize != 0x4C:
                    raise ShortCutError("MS Shortcut HeaderSize = {}, but required to be = {}: shortcut = {}"
                                       .format(hdrSize, 0x4C, str(lnkPath)))
    
                # verify LinkCLSID id (16 bytes)            
                self._clsid = bytes(struct.unpack('B'*16, content[0x04:0x14]))
                if self._clsid != requiredCLSID:
                    raise ShortCutError("MS Shortcut ClassID = {}, but required to be = {}: shortcut = {}"
                                       .format(self._clsid, requiredCLSID, str(lnkPath)))        
    
                # read the LinkFlags structure (4 bytes)
                self._lnkFlags = struct.unpack('I', content[0x14:0x18])[0]
                #logger.debug("lnkFlags=0x%0.8x" % self._lnkFlags)
                position = 0x4C
    
                # if HasLinkTargetIDList bit, then position to skip the stored IDList structure and header
                if (self._lnkFlags & 0x01):
                    idListSize = struct.unpack('H', content[position:position+0x02])[0]
                    position += idListSize + 2
    
                # if HasLinkInfo, then process the linkinfo structure  
                if (self._lnkFlags & 0x02):
                    (linkInfoSize, linkInfoHdrSize, self._linkInfoFlags, 
                     volIdOffset, localBasePathOffset, 
                     cmnNetRelativeLinkOffset, cmnPathSuffixOffset) = struct.unpack('IIIIIII', content[position:position+28])
    
                    # check for optional offsets
                    localBasePathOffsetUnicode = None
                    cmnPathSuffixOffsetUnicode = None
                    if linkInfoHdrSize >= 0x24:
                        (localBasePathOffsetUnicode, cmnPathSuffixOffsetUnicode) = struct.unpack('II', content[position+28:position+36])
    
                    #logger.debug("0x%0.8X" % linkInfoSize)
                    #logger.debug("0x%0.8X" % linkInfoHdrSize)
                    #logger.debug("0x%0.8X" % self._linkInfoFlags)
                    #logger.debug("0x%0.8X" % volIdOffset)
                    #logger.debug("0x%0.8X" % localBasePathOffset)
                    #logger.debug("0x%0.8X" % cmnNetRelativeLinkOffset)
                    #logger.debug("0x%0.8X" % cmnPathSuffixOffset)
                    #logger.debug("0x%0.8X" % localBasePathOffsetUnicode)
                    #logger.debug("0x%0.8X" % cmnPathSuffixOffsetUnicode)
    
                    # if info has a localBasePath
                    if (self._linkInfoFlags & 0x01):
                        bpPosition = position + localBasePathOffset
    
                        # not debugged - don't know if this works or not
                        self._localBasePath = UnpackZ('z', content[bpPosition:])[0].decode('ascii')
                        #logger.debug("localBasePath: {}".format(self._localBasePath))
    
                        if localBasePathOffsetUnicode:
                            bpPosition = position + localBasePathOffsetUnicode
                            self._localBasePath = UnpackUnicodeZ('z', content[bpPosition:])[0]
                            self._localBasePath = self._localBasePath.decode('utf-16')               
                            #logger.debug("localBasePathUnicode: {}".format(self._localBasePath))
    
                    # get common Path Suffix
                    cmnSuffixPosition = position + cmnPathSuffixOffset               
                    self._commonPathSuffix = UnpackZ('z', content[cmnSuffixPosition:])[0].decode('ascii')
                    #logger.debug("commonPathSuffix: {}".format(self._commonPathSuffix))
                    if cmnPathSuffixOffsetUnicode:
                        cmnSuffixPosition = position + cmnPathSuffixOffsetUnicode
                        self._commonPathSuffix = UnpackUnicodeZ('z', content[cmnSuffixPosition:])[0]
                        self._commonPathSuffix = self._commonPathSuffix.decode('utf-16')               
                        #logger.debug("commonPathSuffix: {}".format(self._commonPathSuffix))
    
    
                    # check for CommonNetworkRelativeLink
                    if (self._linkInfoFlags & 0x02):
                        relPosition = position + cmnNetRelativeLinkOffset
                        self._commonNetworkRelativeLink = CommonNetworkRelativeLink(content, relPosition)
    
                    position += linkInfoSize 
    
                # If HasName
                if (self._lnkFlags & 0x04):
                    (position, self._name) = self.readStringObj(content, position)
                    #logger.debug("name: {}".format(self._name))     
    
                # get relative path string
                if (self._lnkFlags & 0x08):
                    (position, self._relativePath) = self.readStringObj(content, position)
                    #logger.debug("relPath='{}'".format(self._relativePath))
    
                # get working dir string
                if (self._lnkFlags & 0x10):
                    (position, self._workingDir) = self.readStringObj(content, position)
                    #logger.debug("workingDir='{}'".format(self._workingDir))
    
                # get command line arguments
                if (self._lnkFlags & 0x20):
                    (position, self._commandLineArgs) = self.readStringObj(content, position)
                    #logger.debug("commandLineArgs='{}'".format(self._commandLineArgs))
    
                # get icon location
                if (self._lnkFlags & 0x40):
                    (position, self._iconLocation) = self.readStringObj(content, position)
                    #logger.debug("iconLocation='{}'".format(self._iconLocation))
    
                # look for environment properties             
                if (self._lnkFlags & 0x200):
                    while True:
                        size = struct.unpack('I', content[position:position+4])[0]
                        #logger.debug("blksize=%d" % size)
                        if size==0: break
    
                        signature = struct.unpack('I', content[position+4:position+8])[0]
                        #logger.debug("signature=0x%0.8x" % signature)     
    
                        # EnvironmentVariableDataBlock          
                        if signature == 0xA0000001:  
    
                            if (self._lnkFlags & 0x80): # unicode
                                self._envTarget = UnpackUnicodeZ('z', content[position+268:])[0]
                                self._envTarget = self._envTarget.decode('utf-16')
                            else:
                                self._envTarget = UnpackZ('z', content[position+8:])[0].decode('ascii')
    
                            #logger.debug("envTarget='{}'".format(self._envTarget))
    
                        position += size
    
    
        def readStringObj(self, scContent, position):
            """ 
            returns:
                tuple: (newPosition, string)
            """
            strg = ''
            size = struct.unpack('H', scContent[position:position+2])[0]
            #logger.debug("workingDirSize={}".format(size))
            if (self._lnkFlags & 0x80): # unicode
                size *= 2
                strg = struct.unpack(str(size)+'s', scContent[position+2:position+2+size])[0]
                strg = strg.decode('utf-16')               
            else:
                strg = struct.unpack(str(size)+'s', scContent[position+2:position+2+size])[0].decode('ascii')
            #logger.debug("strg='{}'".format(strg))
            position += size + 2 # 2 bytes to account for CountCharacters field
    
            return (position, strg)
    
    
    
    
    class CommonNetworkRelativeLink():
    
        def __init__(self, scContent, linkContentPos):
    
            self._networkProviderType = None
            self._deviceName = None
            self._netName = None
    
            (linkSize, flags, netNameOffset, 
             devNameOffset, self._networkProviderType) = struct.unpack('IIIII', scContent[linkContentPos:linkContentPos+20])
    
            #logger.debug("netnameOffset = {}".format(netNameOffset))
            if netNameOffset > 0x014:
                (netNameOffsetUnicode, devNameOffsetUnicode) = struct.unpack('II', scContent[linkContentPos+20:linkContentPos+28])
                #logger.debug("netnameOffsetUnicode = {}".format(netNameOffsetUnicode))
                self._netName = UnpackUnicodeZ('z', scContent[linkContentPos+netNameOffsetUnicode:])[0]
                self._netName = self._netName.decode('utf-16')  
                self._deviceName = UnpackUnicodeZ('z', scContent[linkContentPos+devNameOffsetUnicode:])[0]
                self._deviceName = self._deviceName.decode('utf-16')               
            else:
                self._netName = UnpackZ('z', scContent[linkContentPos+netNameOffset:])[0].decode('ascii')
                self._deviceName = UnpackZ('z', scContent[linkContentPos+devNameOffset:])[0].decode('ascii')
    
        @property    
        def deviceName(self):
            return self._deviceName
    
        @property    
        def netName(self):
            return self._netName
    
        @property    
        def networkProviderType(self):
            return self._networkProviderType
    
    
    
    def UnpackZ (fmt, buf) :
        """
        Unpack Null Terminated String
        """
        #logger.debug(bytes(buf))
    
        while True :
            pos = fmt.find ('z')
            if pos < 0 :
                break
            z_start = struct.calcsize (fmt[:pos])
            z_len = buf[z_start:].find(b'\0')
            #logger.debug(z_len)
            fmt = '%s%dsx%s' % (fmt[:pos], z_len, fmt[pos+1:])
            #logger.debug("fmt='{}', len={}".format(fmt, z_len))
        fmtlen = struct.calcsize(fmt)
        return struct.unpack (fmt, buf[0:fmtlen])
    
    
    def UnpackUnicodeZ (fmt, buf) :
        """
        Unpack Null Terminated String
        """
        #logger.debug(bytes(buf))
        while True :
            pos = fmt.find ('z')
            if pos < 0 :
                break
            z_start = struct.calcsize (fmt[:pos])
            # look for null bytes by pairs
            z_len = 0
            for i in range(z_start,len(buf),2):
                if buf[i:i+2] == b'\0\0':
                    z_len = i-z_start
                    break
    
            fmt = '%s%dsxx%s' % (fmt[:pos], z_len, fmt[pos+1:])
           # logger.debug("fmt='{}', len={}".format(fmt, z_len))
        fmtlen = struct.calcsize(fmt)
        return struct.unpack (fmt, buf[0:fmtlen])
    

    I hope this helps others as well. Thanks

    0 讨论(0)
  • 2020-11-28 04:56

    It's easy as opening ".exe" file. Here also, we are going to use the os module for this. You just have to create a shortcut .lnk and store it in any folder of your choice. Then, in any Python file, first import the os module (already installed, just import). Then, use a variable, say path, and assign it a string value containing the location of your .lnk file. Just create a shortcut of your desired application. At last, we will use os.startfile() to open our shortcut.

    Points to remember:

    1. The location should be within double inverted commas.
    2. Most important, open Properties. Then, under that, open "Details". There, you can get the exact name of your shortcut. Please write that name with ".lnk" at last.

    Now, you have completed the procedure. I hope it helps you. For additional assistance, I am leaving my code for this at the bottom.

    import os
    
    path = "C:\\Users\\hello\\OneDrive\\Desktop\\Shortcuts\\OneNote for Windows 10.lnk"
    
    os.startfile(path)
    

    In my code, I used path as variable and I had created a shortcut for OneNote. In path, I defined the location of OneNote's shortcut. So when I use os.startfile(path), the os module is going to open my shortcut file defined in variable path.

    0 讨论(0)
  • 2020-11-28 05:03

    I didn't really like any of the answers available because I didn't want to keep importing more and more libraries and the 'shell' option was spotty on my test machines. I opted for reading the ".lnk" in and then using a regular expression to read out the path. For my purposes, I am looking for pdf files that were recently opened and then reading the content of those files:

    # Example file path to the shortcut
    shortcut = "shortcutFileName.lnk"
    
    # Open the lnk file using the ISO-8859-1 encoder to avoid errors for special characters
    lnkFile = open(shortcut, 'r', encoding = "ISO-8859-1")
    
    # Use a regular expression to parse out the pdf file on C:\
    filePath = re.findall("C:.*?pdf", pdfFile.read(), flags=re.DOTALL)
    
    # Close File
    lnkFile.close()
    
    # Read the pdf at the lnk Target
    pdfFile = open(tmpFilePath[0], 'rb')
    
    0 讨论(0)
  • 2020-11-28 05:05

    I know this is an older thread but I feel that there isn't much information on the method that uses the link specification as noted in the original question.

    My shortcut target implementation could not use the win32com module and after a lot of searching, decided to come up with my own. Nothing else seemed to accomplish what I needed under my restrictions. Hopefully this will help other folks in this same situation.

    It uses the binary structure Microsoft has provided for MS-SHLLINK.

    import struct
    
    path = 'myfile.txt.lnk'    
    target = ''
    
    with open(path, 'rb') as stream:
        content = stream.read()
        # skip first 20 bytes (HeaderSize and LinkCLSID)
        # read the LinkFlags structure (4 bytes)
        lflags = struct.unpack('I', content[0x14:0x18])[0]
        position = 0x18
        # if the HasLinkTargetIDList bit is set then skip the stored IDList 
        # structure and header
        if (lflags & 0x01) == 1:
            position = struct.unpack('H', content[0x4C:0x4E])[0] + 0x4E
        last_pos = position
        position += 0x04
        # get how long the file information is (LinkInfoSize)
        length = struct.unpack('I', content[last_pos:position])[0]
        # skip 12 bytes (LinkInfoHeaderSize, LinkInfoFlags, and VolumeIDOffset)
        position += 0x0C
        # go to the LocalBasePath position
        lbpos = struct.unpack('I', content[position:position+0x04])[0]
        position = last_pos + lbpos
        # read the string at the given position of the determined length
        size= (length + last_pos) - position - 0x02
        temp = struct.unpack('c' * size, content[position:position+size])
        target = ''.join([chr(ord(a)) for a in temp])
    
    0 讨论(0)
提交回复
热议问题