'Copying files usingwin32com.shell pidl reference in Windows - to copy picture files off an iPhone

I've been trying to find a way to copy files off an iPhone but despite being able to list files within it using win32com.shell GetDisplayNameOf and BindToObject, I can't seem to find any way of copying files using only the pidl reference.

Unfortunately when the iPhone is connected to Windows it doesn't mount as a drive letter, requiring you to use the PIDL reference rather than a full path. However after much Googling, I can't seem to find a way of copying using only the PIDL reference.

"path = shell.SHGetPathFromIDList(pidl)" seems to be a potential option to find a filesystem path from a PIDL, however as the iPhone doesn't mount to a filesystem path, this always returns an error when given a PIDL value.

Example code I've used so far (taken from How can I iterate across the photos on my connected iPhone from Windows 7 in Python?):

from win32com.shell import shell, shellcon

desktop = shell.SHGetDesktopFolder()

for pidl in desktop:
    if desktop.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "This PC":
        # path = shell.SHGetPathFromIDList(pidl)
        print path
        break

folder = desktop.BindToObject(pidl, None, shell.IID_IShellFolder)
for pidl in folder:
     if folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "My iPhone":
         break

folder = folder.BindToObject(pidl, None, shell.IID_IShellFolder)
for pidl in folder:
    if folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "Internal Storage":
        break
        
folder = folder.BindToObject(pidl, None, shell.IID_IShellFolder)
for pidl in folder:
    if folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "DCIM":
        break

# Each of the image folders
folder = folder.BindToObject(pidl, None, shell.IID_IShellFolder)
for pidl in folder:
    # print folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL)

    files = folder.BindToObject(pidl, None, shell.IID_IShellFolder)

    for curfile in files:
        current_file = folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) + "\\" + files.GetDisplayNameOf(curfile, shellcon.SHGDN_NORMAL)
        folder.BindToObject(pidl, )

Does anyone have any ideas?

Key references I've used so far: Example code that correctly reads the filesystem using PIDLs: How can I iterate across the photos on my connected iPhone from Windows 7 in Python?

Example of how to list files off an iPhon, but no details of how to copy them: https://github.com/dblume/list-photos-on-phone/blob/master/list-photos-on-phone.py



Solution 1:[1]

Thanks for the above information, with it I was able to make a python implementation for moving photos from iPhone X to Windows10 PC. Key functions below

# imports probably needed
from win32com.shell import shell, shellcon
from win32com.propsys import propsys
import pythoncom

# Recursive function to browse into a non filesystem path
def recurse_and_get_ishellfolder(base_ishellfolder, path):
    splitted_path = path.split("\\", 1)

    for pidl in base_ishellfolder:
        if base_ishellfolder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == splitted_path[0]:
            break

    folder = base_ishellfolder.BindToObject(pidl, None, shell.IID_IShellFolder)

    if len(splitted_path) > 1:
        # More to recurse
        return recurse_and_get_ishellfolder(folder, splitted_path[1])
    else:
        return folder


# How to move non filesystem file to filesystem patj
def move_file_by_pidl_to_path(src_ishellfolder, src_pidl, dst_path, dst_filename):
    pidl_folder_dst, flags = shell.SHILCreateFromPath(dst_path, 0)
    dst_ishellfolder = shell.SHGetDesktopFolder().BindToObject(pidl_folder_dst, None, shell.IID_IShellFolder)

    fidl = shell.SHGetIDListFromObject(src_ishellfolder)  # Grab the PIDL from the folder object
    didl = shell.SHGetIDListFromObject(dst_ishellfolder)  # Grab the PIDL from the folder object

    si = shell.SHCreateShellItem(fidl, None, src_pidl)  # Create a ShellItem of the source file
    dst = shell.SHCreateItemFromIDList(didl)

    pfo = pythoncom.CoCreateInstance(shell.CLSID_FileOperation, None, pythoncom.CLSCTX_ALL, shell.IID_IFileOperation)
    pfo.SetOperationFlags(shellcon.FOF_NOCONFIRMATION | shellcon.FOF_SILENT | shellcon.FOF_NOERRORUI)
    pfo.MoveItem(si, dst, dst_filename) # Schedule an operation to be performed
    pfo.PerformOperations()
    return not pfo.GetAnyOperationsAborted()


# Bonus: get file modification datetime for a non filesystem file
DATE_PROP_KEY = propsys.PSGetPropertyKeyFromName("System.DateModified")
DATE_PROP_PARSE_STR = '%Y/%m/%d:%H:%M:%S.%f' # not sure bout the f modifier but it does not really matter
def getmodified_datetime_by_pidl(src_ishellfolder, src_pidl):
    fidl = shell.SHGetIDListFromObject(src_ishellfolder)  # Grab the PIDL from the folder object
    si = shell.SHCreateShellItem(fidl, None, src_pidl)  # Create a ShellItem of the source file
    ps = propsys.PSGetItemPropertyHandler(si)
    date_str = ps.GetValue(DATE_PROP_KEY).ToString()
    return datetime.strptime(date_str, DATE_PROP_PARSE_STR)


# Example photo moving main logic
def move_files():
    main_folder = recurse_and_get_ishellfolder(shell.SHGetDesktopFolder(), "This Pc\\path\\to\\DCIM")

    for photo_folder_pidl in main_folder:
        folder_name = main_folder.GetDisplayNameOf(photo_folder_pidl, shellcon.SHGDN_NORMAL)
        folder = main_folder.BindToObject(photo_folder_pidl, None, shell.IID_IShellFolder)
        for pidl in folder:
            child_name = folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL)

            file_mod_date = getmodified_datetime_by_pidl(folder, pidl)
            if not older_than_datetime or file_mod_date < older_than_datetime:
                print("Transferring file: " + child_name)
                move_file_by_pidl_to_path(...)
            else:
                print("Skipping too recent file: " + child_name)

Full script for moving photos: https://gitlab.com/lassi.niemisto/iphone-photo-dump

EDIT: Key parts from linked implementation copied here as code

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1