Чтение цели файла .lnk в Python? - PullRequest
24 голосов
/ 29 декабря 2008

Я пытаюсь прочитать целевой файл / каталог ярлыка (.lnk) из Python. Есть ли способ избавиться от головной боли? Спецификация .lnk [PDF] находится у меня над головой. Я не против использования только API для Windows.

Моя конечная цель - найти папку "(My) Videos" в Windows XP и Vista. В XP по умолчанию он равен %HOMEPATH%\My Documents\My Videos, а в Vista - %HOMEPATH%\Videos. Тем не менее, пользователь может переместить эту папку. В этом случае папка %HOMEPATH%\Videos перестает существовать и заменяется на %HOMEPATH%\Videos.lnk, которая указывает на новую папку "My Videos". И я хочу его абсолютное местоположение.

Ответы [ 5 ]

36 голосов
/ 21 февраля 2009

Создание ярлыка с использованием Python (через WSH)

import sys
import win32com.client 

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


Считывание цели ярлыка с использованием Python (через WSH)

import sys
import win32com.client 

shell = win32com.client.Dispatch("WScript.Shell")
shortcut = shell.CreateShortCut("t:\\test.lnk")
print(shortcut.Targetpath)
9 голосов
/ 29 декабря 2008

Кроме того, вы можете попробовать использовать SHGetFolderPath () . Следующий код может работать, но я сейчас не на Windows-машине, поэтому не могу его протестировать.

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
9 голосов
/ 29 декабря 2008

В основном вызывайте Windows API напрямую. Вот хороший пример, найденный после поиска в Google:

import os, sys
import pythoncom
from win32com.shell import shell, shellcon

shortcut = pythoncom.CoCreateInstance (
  shell.CLSID_ShellLink,
  None,
  pythoncom.CLSCTX_INPROC_SERVER,
  shell.IID_IShellLink
)
desktop_path = shell.SHGetFolderPath (0, shellcon.CSIDL_DESKTOP, 0, 0)
shortcut_path = os.path.join (desktop_path, "python.lnk")
persist_file = shortcut.QueryInterface (pythoncom.IID_IPersistFile)
persist_file.Load (shortcut_path)

shortcut.SetDescription ("Updated Python %s" % sys.version)
mydocs_path = shell.SHGetFolderPath (0, shellcon.CSIDL_PERSONAL, 0, 0)
shortcut.SetWorkingDirectory (mydocs_path)

persist_file.Save (shortcut_path, 0)

Это от http://timgolden.me.uk/python/win32_how_do_i/create-a-shortcut.html.

Вы можете выполнить поиск по "python ishelllink" для других примеров.

Также справка по API тоже помогает: http://msdn.microsoft.com/en-us/library/bb774950(VS.85).aspx

6 голосов
/ 10 марта 2015

Я знаю, что это старая ветка, но я чувствую, что не так много информации о методе, который использует спецификацию ссылки, как отмечено в исходном вопросе

Моя целевая реализация ярлыка не смогла использовать модуль win32com и после долгих поисков решила найти свой собственный. Ничто иное, казалось, не достигло того, что мне было нужно под моими ограничениями. Надеюсь, это поможет другим людям в этой же ситуации.

Используется двоичная структура, предоставленная Microsoft для 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])
2 голосов
/ 19 апреля 2016

Я также понимаю, что этот вопрос старый, но я нашел ответы, наиболее подходящие для моей ситуации.

Как и ответ Джареда, я также не смог использовать модуль win32com. Так что использование Джаредом двоичной структуры из MS-SHLLINK помогло мне в этом разобраться. Мне нужно было прочитать ярлыки как в Windows, так и в Linux, где ярлыки создаются на общем ресурсе самбы Windows. Реализация Джареда не совсем сработала для меня, я думаю, только потому, что я столкнулся с некоторыми различными вариациями формата ярлыков. Но это дало мне начало, в котором я нуждался (спасибо Джареду).

Итак, вот класс с именем MSShortcut, который расширяет подход Джареда. Однако реализация только Python3.4 и выше, из-за использования некоторых функций pathlib, добавленных в 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: /368504/chtenie-tseli-faila-lnk-v-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])

Надеюсь, это поможет и другим. Спасибо

...