Detect media insertion on Windows in Python - python

I need a program that detects media insertion and also tells me the drive letter so that I can build on it and add other functions to be run when the device inserted event is fired.
I think it can be done using WMI using the Win32_VolumeChangeEvent class (I found some implementations in Powershell and C# but I want to do it with Python). I know there is also the wmi module for python eventually and I found this snippet of code from a Python mailing list but it seems it doesn't work.
Then I also found this Python script that could do what I need. It seems it was written for python 2 and I adjusted the parenthesis for the print() function in order to make it work on python 3, besides I noticed there were a couple of unnecessary ; in the code. (maybe it was ported from C and the developer left them there by mistake. This python script uses ctypes).
I show you the code I got:
import win32api, win32con, win32gui
from ctypes import *
#
# Device change events (WM_DEVICECHANGE wParam)
#
DBT_DEVICEARRIVAL = 0x8000
DBT_DEVICEQUERYREMOVE = 0x8001
DBT_DEVICEQUERYREMOVEFAILED = 0x8002
DBT_DEVICEMOVEPENDING = 0x8003
DBT_DEVICEREMOVECOMPLETE = 0x8004
DBT_DEVICETYPESSPECIFIC = 0x8005
DBT_CONFIGCHANGED = 0x0018
#
# type of device in DEV_BROADCAST_HDR
#
DBT_DEVTYP_OEM = 0x00000000
DBT_DEVTYP_DEVNODE = 0x00000001
DBT_DEVTYP_VOLUME = 0x00000002
DBT_DEVTYPE_PORT = 0x00000003
DBT_DEVTYPE_NET = 0x00000004
#
# media types in DBT_DEVTYP_VOLUME
#
DBTF_MEDIA = 0x0001
DBTF_NET = 0x0002
WORD = c_ushort
DWORD = c_ulong
class DEV_BROADCAST_HDR(Structure):
_fields_ = [
("dbch_size", DWORD),
("dbch_devicetype", DWORD),
("dbch_reserved", DWORD)
]
class DEV_BROADCAST_VOLUME(Structure):
_fields_ = [
("dbcv_size", DWORD),
("dbcv_devicetype", DWORD),
("dbcv_reserved", DWORD),
("dbcv_unitmask", DWORD),
("dbcv_flags", WORD)
]
def drive_from_mask(mask):
n_drive = 0
while 1:
if (mask & (2 ** n_drive)):
return n_drive
else:
n_drive += 1
class Notification:
def __init__(self):
message_map = {
win32con.WM_DEVICECHANGE: self.onDeviceChange
}
wc = win32gui.WNDCLASS()
hinst = wc.hInstance = win32api.GetModuleHandle(None)
wc.lpszClassName = "DeviceChangeDemo"
wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW
wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
wc.hbrBackground = win32con.COLOR_WINDOW
wc.lpfnWndProc = message_map
classAtom = win32gui.RegisterClass(wc)
style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
self.hwnd = win32gui.CreateWindow(
classAtom,
"Device Change Demo",
style,
0, 0,
win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
0, 0,
hinst, None
)
def onDeviceChange(self, hwnd, msg, wparam, lparam):
#
# WM_DEVICECHANGE:
# wParam - type of change: arrival, removal etc.
# lParam - what's changed?
# if it's a volume then...
# lParam - what's changed more exactly
#
dev_broadcast_hdr = DEV_BROADCAST_HDR.from_address(lparam)
if wparam == DBT_DEVICEARRIVAL:
print("Something's arrived")
if dev_broadcast_hdr.dbch_devicetype == DBT_DEVTYP_VOLUME:
print("It's a volume!")
dev_broadcast_volume = DEV_BROADCAST_VOLUME.from_address(lparam)
if dev_broadcast_volume.dbcv_flags & DBTF_MEDIA:
print("with some media")
drive_letter = drive_from_mask(dev_broadcast_volume.dbcv_unitmask)
print("in drive", chr(ord("A") + drive_letter))
return 1
if __name__ == '__main__':
w = Notification()
win32gui.PumpMessages()
Windows sends all top-level windows a set of default WM_DEVICECHANGE messages when new devices or media (such as a CD or DVD) are added and become available, and when existing devices or media are removed.
Each WM_DEVICECHANGE message has an associated event that describes the change, and a structure that provides detailed information about the change. The structure consists of an event-independent header, DEV_BROADCAST_HDR, followed by event-dependent members. The event-dependent members describe the device to which the event applies. To use this structure, applications must first determine the event type and the device type. Then, they can use the correct structure to take appropriate action.
When the user inserts a new CD or DVD into a drive, applications receive a WM_DEVICECHANGE message with a DBT_DEVICEARRIVAL event. The application must check the event to ensure that the type of device arriving is a volume (the dbch_devicetype member is DBT_DEVTYP_VOLUME) and that the change affects the media (the dbcv_flags member is DBTF_MEDIA).
Here you can find an implementation in C++ directly from Microsoft MSDN.
PROBLEMS:
The code compiles without errors and if I insert a USB drive I get the message "Something's arrived" and "It's a volume!" correctly but the message "with some media" and the drive letter are never displayed so it's like this part of the code doesn't work:
dev_broadcast_volume = DEV_BROADCAST_VOLUME.from_address(lparam)
if dev_broadcast_volume.dbcv_flags & DBTF_MEDIA:
print("with some media")
drive_letter = drive_from_mask(dev_broadcast_volume.dbcv_unitmask)
print("in drive", chr(ord("A") + drive_letter))
I need to fix the program in order to know also the drive letter of the new media inserted.
UPDATE:
I tried to print the value of dev_broadcast_volume.dbcv_flags and it's 0.
Then I tried to print the value of DBTF_MEDIA and it's 1.
I see that in the code there is an if statement with a bitwise operation:
if dev_broadcast_volume.dbcv_flags & DBTF_MEDIA:
If both dev_broadcast_volume.dbcv_flags and DBTF_MEDIA were == 1, the bitwise operation would return 1, so the if statement would be True and the code inside would be executed but dev_broadcast_volume.dbcv_flags == 0 so the bitwise operation would return 0 and the if statement is False and the code won't get executed, right?
I tried to remove the if statement entirely and although the check doesn't exist anymore (is it necessary?), now the drive letter is printed correctly.
This is the output of the program I get now:
Something's arrived
It's a volume!
in drive K

Related

Setting up ZKTeco ZK8500 (ZK9500) fingerprint scanner in Python

I am working on a Python project that includes using Images taken by Fingerprint scanner and processing them. I am currently using ZKTeco ZK8500(ZK9500) that is provided with C SDK library. But i have very little knowledge in C/C++ and to be honest Python is not my strongest language. I am trying to use C SDK via ctypes library in Python but i have some difficulties with that. I am currently testing on Windows, installed SDK from official site. So far my code looks like:
import ctypes
from ctypes import *
import logging
logging.basicConfig(filename='app_fp.log',
filemode='a',
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%d-%m-%Y %H:%M:%S',
level=logging.INFO)
lib_3 = CDLL('libzkfp.dll')
###############################
lib_3.ZKFPM_Init.restype = ctypes.c_int
lib_3.ZKFPM_Terminate.restype = ctypes.c_int
lib_3.ZKFPM_GetDeviceCount.restype = ctypes.c_int
lib_3.ZKFPM_OpenDevice.restype = ctypes.c_void_p
lib_3.ZKFPM_OpenDevice.argtypes = [ctypes.c_int]
lib_3.ZKFPM_CloseDevice.restype = ctypes.c_int
lib_3.ZKFPM_CloseDevice.argtypes = [ctypes.c_void_p]
lib_3.ZKFPM_GetParameters.restype = ctypes.c_int
lib_3.ZKFPM_GetParameters.argtypes = [ctypes.c_void_p,
ctypes.c_int,
ctypes.POINTER(ctypes.c_ubyte),
ctypes.POINTER(ctypes.c_uint),
]
paramChar = c_ubyte()
paramInt = c_uint()
################################
res_init = lib_3.ZKFPM_Init()
print("Init:", res_init)
res_getDeviceCount = lib_3.ZKFPM_GetDeviceCount()
print("Dev count:", res_getDeviceCount, type(res_getDeviceCount))
dev0_handle = lib_3.ZKFPM_OpenDevice(0)
print("Dev HANDLE:", dev0_handle)
dev0_getParam = lib_3.ZKFPM_GetParameters(dev0_handle,
1102,
byref(paramChar),
byref(paramInt))
print("Get dev name:", dev0_getParam, paramChar, sizeof(paramChar))
logging.info("DeviceHandle:" + str(dev0_handle))
res_close = lib_3.ZKFPM_CloseDevice(dev0_handle)
print("Close dev:", dev0_handle, res_close)
#
res_terminate = lib_3.ZKFPM_Terminate()
print("Terminate:", res_terminate)
Stdout looks like this:
Init: 0
Dev count: 2 <class 'int'>
Dev HANDLE: 2682398313632
Get dev name: -3 c_ubyte(0) 1
Close dev: 2682398313632 0
Terminate: 0
Check sum data is true!sum=9286, buf[val]=9286
Check sum data is true!sum=-44, buf[val]=-44
opts->Stripe_Reference_Point_X=654, opts->Stripe_Reference_Point_Y=542
Check sum data is true!sum=908, buf[val]=908
CMOS Sensor->Exposure:256, RedGain:152, GreenGain1:200, GreenGain2:187,BlueGain:112.
Main1LED:200, Main2LED:200, Side1LED:175, Side2LED:175, Anti1LED:200, Anti2LED:200.
start to set the exposure parameters
Process finished with exit code 0
Description of GetParameters function from SDK is:
5.2.7 ZKFPM_GetParameters
[Function]
int APICALL ZKFPM_GetParameters(HANDLE hDevice, int nParamCode, unsigned
char* paramValue, unsigned int* cbParamValue);
[Purpose]
This function is used to acquire fingerprint reader parameters.
[Parameter Description]
hDevice
Device operation instance handle
nParamCode
Parameter code (For details, see the parameter code list.)
paramValue [out]
Returned parameter value
cbParamValue [in/out]
[in] Memory size allocated based on nParamCode
[out]Data size of the returned parameter value
[Return Value]
0 Succeeded
Others Failed (See the Appendixes.)
Questions:
I do not understand why i can OpenDevice, can CloseDevice but get Get dev name: -3 c_ubyte(0) 1 when i try to get parameter i get -3- No device connected. Maybe something in my Python declaration or variable types?
How i can get image to file from fingerprint scanner? Code snippets if anybody knows?

Simplifying Ctype union in Python (to send Keyboard events in Windows)

I have the following script that sends a keyboard key every xx seconds (F15 by default), inspired by some code found online
I want to remove the types and union related to Mouse events but cannot make it to work. In particular, I don't know how to remove the class MOUSEINPUT and the union of types _INPUTunion (removing them will stop sending the keyboard key).
Any suggestion on how to trim the script to a minimum (i.e. only keep code related to Keyboard)?
The following code will send the key "C" to be able to debug.
#!/python
import ctypes
import sys
import time
LONG = ctypes.c_long
DWORD = ctypes.c_ulong
ULONG_PTR = ctypes.POINTER(DWORD)
WORD = ctypes.c_ushort
class MOUSEINPUT(ctypes.Structure):
_fields_ = (
('dx', LONG), ('dy', LONG), ('mouseData', DWORD),
('dwFlags', DWORD), ('time', DWORD),
('dwExtraInfo', ULONG_PTR)
)
class KEYBDINPUT(ctypes.Structure):
_fields_ = (
('wVk', WORD), ('wScan', WORD),
('dwFlags', DWORD), ('time', DWORD),
('dwExtraInfo', ULONG_PTR)
)
class _INPUTunion(ctypes.Union):
_fields_ = (('mi', MOUSEINPUT), ('ki', KEYBDINPUT))
class INPUT(ctypes.Structure):
_fields_ = (('type', DWORD), ('union', _INPUTunion))
def SendInput(*inputs):
print(inputs[0].union.mi)
nInputs = len(inputs)
LPINPUT = INPUT * nInputs
pInputs = LPINPUT(*inputs)
cbSize = ctypes.c_int(ctypes.sizeof(INPUT))
return ctypes.windll.user32.SendInput(nInputs, pInputs, cbSize)
INPUT_KEYBOARD = 1
def Input(structure):
if isinstance(structure, KEYBDINPUT):
return INPUT(INPUT_KEYBOARD, _INPUTunion(ki=structure))
else:
raise TypeError('Cannot create INPUT structure (keyboard)!')
def Keyboard(code, flags=0):
return Input(KEYBDINPUT(code, code, flags, 0, None))
if __name__ == '__main__':
nb_cycles = 10
while nb_cycles != 0:
time.sleep(2) # 3 seconds
# Key "c" for debug, but ideally use 0x7E for "F15"
SendInput(Keyboard(ord("C")))
sys.stdout.write(".")
nb_cycles -= 1
"The Bible" for tasks like this: [Python.Docs]: ctypes - A foreign function library for Python. I modified your code (found a bunch of problems, out of which some were critical).
code00.py:
#!/usr/bin/env python
import ctypes as cts
import sys
import time
from ctypes import wintypes as wts
wts.BYTE = cts.c_ubyte
class KEYBDINPUT(cts.Structure):
_fields_ = (
("wVk", wts.WORD),
("wScan", wts.WORD),
("dwFlags", wts.DWORD),
("time", wts.DWORD),
("dwExtraInfo", wts.PULONG),
)
class INPUT(cts.Structure):
_fields_ = (
("type", wts.DWORD),
("ki", KEYBDINPUT),
("padding", wts.BYTE * 8)
)
INPUT_KEYBOARD = 1 # Also defined by win32con if you have pywin32 installed
INPUT_LEN = cts.sizeof(INPUT)
LPINPUT = cts.POINTER(INPUT)
user32_dll = cts.windll.user32
SendInput = user32_dll.SendInput
SendInput.argtypes = (wts.UINT, LPINPUT, cts.c_int)
SendInput.restype = wts.UINT
def send_input(_input):
return SendInput(1, cts.byref(_input), INPUT_LEN)
def keyboard(code, flags=0):
return INPUT(INPUT_KEYBOARD, (KEYBDINPUT(code, code, flags, 0, None)))
def main(*argv):
time.sleep(2)
nb_cycles = 3
for _ in range(nb_cycles):
time.sleep(0.5) # 3 seconds
# Key "c" for debug, but ideally use 0x7E for "F15"
ret = send_input(keyboard(ord("C")))
#print(ret)
sys.stdout.write(".")
sys.stdout.flush()
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
Notes:
Initially, I defined the INPUT structure having only its 1st 2 members, but took a look at [MS.Learn]: INPUT structure (winuser.h) and noted that the union consists of:
MOUSEINPUT
KEYBDINPUT
HARDWAREINPUT
Did some tests and noted that MOUSEINPUT has the largest size out of the 3 structs, and it's 8 bytes greater than KEYBDINPUT (for both 032bit and 064bit), so I added the (dummy) (padding) member. Normally, I wouldn't expect that SendInput to go beyond KEYBDINPUT size when receiving an INPUT structure with type set to INPUT_KEYBOARD, but the INPUT size is required by [MS.Docs]: SendInput function (winuser.h)'s cbSize arg
def SendInput(*inputs): - in Python, Asterisk (*) before an argument does something totally different than in C. Check [SO]: Expanding tuples into arguments (I didn't find the official .doc). I modified the function so that it only sends one such structure
Check [SO]: C function called from Python via ctypes returns incorrect value (#CristiFati's answer) for a common pitfall when working with CTypes (calling functions)
Use ctypes.wintypes for Win specific types, don't reinvent the wheel. Not to mention that sometimes mismatches might occur (most recent one that I saw, was ctypes.c_bool vs. wintypes.BOOL)
wts.BYTE redefinition (not necessary in this case) - a CTypes bug: [SO]: Why ctypes.wintypes.BYTE is signed, but native windows BYTE is unsigned? (#CristiFati's answer)
I renamed your functions to be [Python]: PEP 8 - Style Guide for Python Code compliant. I also removed some of them, that were no longer needed
Other small changes that don't worth being mentioned individually
As for testing, I used a Notepad window (or any other, "sensitive" to user input):
Launch the script
Alt + Tab to the test window (I added the 1st instruction (time.sleep(2)) from main just to give user time to switch windows), and notice the cs "magically" appearing
... Or just launch the script from console, press (and hold) Ctrl, then notice the KeyboardInterrupt (Ctrl + C) occurrence.

Error in loading custom font file for tkinter

Following the question-
Truly custom font in Tkinter,
I tried this code (changing to str and bytes for python 3.x as instructed):
FR_PRIVATE = 0x10
FR_NOT_ENUM = 0x20
def loadfont(fontpath, private=True, enumerable=False):
# This function was taken from
# https://github.com/ifwe/digsby/blob/f5fe00244744aa131e07f09348d10563f3d8fa99/digsby/src/gui/native/win/winfonts.py#L15
# This function is written for Python 2.x. For 3.x, you
# have to convert the isinstance checks to bytes and str
if isinstance(fontpath, bytes):
pathbuf = create_string_buffer(fontpath)
AddFontResourceEx = windll.gdi32.AddFontResourceExA
elif isinstance(fontpath, str):
pathbuf = create_unicode_buffer(fontpath)
AddFontResourceEx = windll.gdi32.AddFontResourceExW
else:
raise TypeError('fontpath must be of type str or unicode')
flags = (FR_PRIVATE if private else 0) | (FR_NOT_ENUM if not enumerable else 0)
numFontsAdded = AddFontResourceEx(byref(pathbuf), flags, 0)
return bool(numFontsAdded)
root = tkinter.Tk()
first = tkinter.font.families()
print(loadfont("Interface/FONTS/BebasNeue.otf"))
second = tkinter.font.families()
print(first==second)
print([x for x in second if x not in first])
root.mainloop()
(I put tkinter.font.families() in the window as it only works in the context of a window)
The output was:
True
[]
I tried swapping the instance str & bytes but this throws an error as well.
I've looked everywhere and I can't find an answer.
UPDATE
The script actually did work, although the font did not show up in font.families(). My mistake was assuming that the font.families() was referencing the environmental fonts and would be updated, when I think it was referencing a variable assigned on the initialisation of the module. When I used it as a font in a test window, it worked perfectly.

Condense Datetime Code, Hook Appindicator3 Menu Call + Relative Icon Path, Code Review

Hello,
I am a novice to this (please answer like so for me). All of the code should run easily after fixing my (second) question. It actually runs great on my machine, but probably not yours yet. I have tried to comment everywhere for you to make it easier for someone to read. This runs on an Ubuntu 12.10 machine, but you don't need to be running Linux to help my issues!
SOLVED 1. Code Review: As you go through the code, I would appreciate any input on how to condense or do things in a better, more appropriate way. The rest of my questions are really just the things I already know should be worked on. But if there's something else you find in my coding style, et al, please be candid. Upvotes to all good comments.
SOLVED: 2. Relative Icon Path: At the following:
/home/mh00h/workspace/issindicator/src/International_Space_Station_clip_art.svg
This is an absolute path to the same folder as this script. I don't want that,, I want this script to work on anybody's machine. I tried these:
$HOME/workspace...
(nothing in front) International_Space_Station_clip_art.svg
./International_Space_Station_clip_art.svg
but those didn't work. The image above is what I am trying to use (yes, I know I have an SVG instead of png listed, imgur limitation). Here is the documentation. It talks about an ""icon-theme-path"... maybe that would do it somehow? Or perhaps there is a standard directory all programmers are expected to store icons?
3. Concentrate my datetime functions: Really, I was fortunate to get this to work at all. My way is roundabout, but as far as I can tell, it works. I'm pretty confident that there is a better way to fix that mess though!! You'll find a bunch of datetime stuff at the bottom of the script.
SOLVED: 4. Appindicator3 Hook: I would love to have GTK refresh only when the menu has been called instead of running every second regardless. This was partially answered here, but I don't really understand how to implement "realize." (Hopefully this is the right place to be asking this?)
Thank you!
#!/usr/bin/env python
import json, urllib2, time, math, datetime, os, webbrowser
from dateutil import tz
#indicator
from gi.repository import Gtk, GObject
from gi.repository import AppIndicator3 as appindicator
class indicator():
def __init__(self):
#######Set this to "False" if IssIndicator should hide it's icon during normal runtime (default = True)
self.isiconhidden = True
#
#create indicator
self.ind = appindicator.Indicator.new (
"issindicator",
"/home/mh00h/workspace/issindicator/src/International_Space_Station_clip_art.svg",
#"indicator-messages",
appindicator.IndicatorCategory.APPLICATION_STATUS)
if self.isiconhidden == True:
self.ind.set_status (appindicator.IndicatorStatus.PASSIVE)
else:
self.ind.set_status (appindicator.IndicatorStatus.ACTIVE)
#this is used to keep track of the gtk refresh period
self.refreshvalue = False
#dropdown menu
#current pass menu items
self.menu = Gtk.Menu()
self.curpass = Gtk.MenuItem("not refreshed")
self.curpass.connect("activate", self.checkiss)
self.menu.append(self.curpass)
self.curpassdur = Gtk.MenuItem(" ")
self.menu.append(self.curpassdur)
self.curpassrise = Gtk.MenuItem(" ")
self.menu.append(self.curpassrise)
self.curpassset = Gtk.MenuItem(" ")
self.menu.append(self.curpassset)
self.sep1 = Gtk.SeparatorMenuItem()
self.menu.append(self.sep1)
#future pass items
self.futpass = Gtk.MenuItem(" ")
self.futpass.connect("activate", self.onurl)
self.menu.append(self.futpass)
self.sep2 = Gtk.SeparatorMenuItem()
self.menu.append(self.sep2)
#Options items
self.aboutmenu = Gtk.MenuItem("About")
self.aboutmenu.connect("activate", self.onabout)
self.menu.append(self.aboutmenu)
self.quit = Gtk.MenuItem("Quit")
self.quit.connect("activate", self.quitnow)
self.menu.append(self.quit)
self.curpass.show()
self.sep1.show()
self.futpass.show()
self.sep2.show()
self.aboutmenu.show()
self.quit.show()
self.ind.set_menu(self.menu)
#get iss data at first run
self.updatecache()
self.checkiss()
Gtk.main()
#functions
def hideicon(self, w=None):
self.ind.set_status (appindicator.IndicatorStatus.PASSIVE)
def showicon(self, w=None):
self.ind.set_status (appindicator.IndicatorStatus.ACTIVE)
def quitnow(self, w=None):
Gtk.main_quit()
#open browser for more tracking info
def onurl(self, w=None):
webbrowser.open("http://www.n2yo.com/passes/")
def onabout(self,widget):
widget.set_sensitive(False)
ad=Gtk.AboutDialog()
ad.set_name("aboutdialog")
ad.set_version("0.1")
ad.set_copyright('Copyrignt (c) 2013 mh00h')
ad.set_comments('Indicating ISS Zarya')
ad.set_license(''+
'This program is free software: you can redistribute it and/or modify it\n'+
'under the terms of the GNU General Public License as published by the\n'+
'Free Software Foundation, either version 3 of the License, or (at your option)\n'+
'any later version.\n\n'+
'This program is distributed in the hope that it will be useful, but\n'+
'WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n'+
'or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for\n'+
'more details.\n\n'+
'You should have received a copy of the GNU General Public License along with\n'+
'this program. If not, see <http://www.gnu.org/licenses/>.')
ad.set_website('https://launchpad.net/~mh00h/+archive/issindicator')
ad.set_website_label('ISSIndicator Homepage')
ad.set_authors(['mh00h <abcd#abcd.com'])
ad.run()
ad.destroy()
widget.set_sensitive(True)
#how often to run checkiss
def setrefresh(self, r):
#this clause is required to keep script from hanging
if r != self.refreshvalue:
self.refreshvalue = r
try:
self.reftime = GObject.source_remove(True)
except:
pass
try:
self.reftime = GObject.timeout_add(r, self.checkiss)
except:
pass
#
def updatecache(self, w=None):
#this will show in the menu until the update process completes
self.passingstatus = 'not updated yet'
#get ISS data from api
self.ip = urllib2.urlopen("http://api.exip.org/?call=ip").read()
self.geoip = json.load(urllib2.urlopen("http://freegeoip.net/json/"+self.ip))
self.data = json.load(urllib2.urlopen("http://api.open-notify.org/iss/?lat="+str(self.geoip["latitude"])+"&lon="+str(self.geoip["longitude"])+"&alt=280&n=47"))
self.data = {"message": "success", "request": {"latitude": 45.0, "passes": 3, "altitude": 280, "longitude": -81.0, "datetime": 1361502063},
"response": [{"duration": 542, "risetime": time.time()+10}, {"duration": 642, "risetime": 1361560774}, {"duration": 593, "risetime": 1361566621}]}
def checkiss(self, w=None):
#so as to not overload api servers, this runs as a separate process
#this updates the timers
self.n = 0
self.passingstatus = "ISS Zarya is below the horizon"
#check if we've gone through cached iss passings and update api if needed
try:
#ignore errors in case internet is not accessible
#have a buffer of 5 passes remaining before updating cache
#at 2 passes left, stop the program to prevent the rest of the program from throwing codes
if time.time() > self.data['response'][len(self.data['response'])-5]['risetime']:
self.updatecache
except:
if time.time() > self.data['response'][len(self.data['response'])-2]['risetime']:
os.system("notify-send 'ISS Indicator tried multiple times to update its satellite cache but has run out of its cached track.' 'This may be due to a bad internet connection. The application will now quit.'")
Gtk.main_quit()
#get current time
current_utc = datetime.datetime.utcnow()
current_utc = current_utc.replace(tzinfo=tz.gettz('UTC'))
#iterate over all iss passes
for k in self.data['response']:
duration = self.data['response'][self.n]['duration']
risetime = self.data['response'][self.n]['risetime']
settime = risetime + duration
#if this iteration matches with the current time, do...
if risetime <= time.time() <= settime:
#make the countdown values for the current pass tick
#rise time calculations and date conversions to string format
currisetime_utc = datetime.datetime.utcfromtimestamp(self.data['response'][self.n]['risetime'])
currisetime_utc = currisetime_utc.replace(tzinfo=tz.gettz('UTC'))
currisetime_tz = currisetime_utc.astimezone(tz.tzlocal())
currisetime_tzstr = str("%02d" % (currisetime_tz.hour))+':'+str("%02d" % (currisetime_tz.minute))+':'+str("%02d" % (currisetime_tz.second))
#set time calculations and durations
cursettime_utc = datetime.datetime.utcfromtimestamp(self.data['response'][self.n]['risetime']+self.data['response'][self.n]['duration'])
cursettime_utc = cursettime_utc.replace(tzinfo=tz.gettz('UTC'))
cursettime_tz = cursettime_utc.astimezone(tz.tzlocal())
curremainingtimeleft = cursettime_utc - current_utc
curduration = cursettime_utc - currisetime_utc
z= curremainingtimeleft.seconds
zhours = z/60/60
zminutes = z/60-zhours*60
zseconds = z-zhours*60*60-zminutes*60
curremainingtimeleftstr = str(zhours)+':'+str("%02d" % (zminutes))+':'+str("%02d" % (zseconds))
z= curduration.seconds
zhours = z/60/60
zminutes = z/60-zhours*60
zseconds = z-zhours*60*60-zminutes*60
curdurationstr = str(zhours)+':'+str("%02d" % (zminutes))+':'+str("%02d" % (zseconds))
cursettime_tzstr = str("%02d" % (cursettime_tz.hour))+':'+str("%02d" % (cursettime_tz.minute))+':'+str("%02d" % (cursettime_tz.second))
#since the ISS is presently overhead, show the icon and update GTK menuitems to show timers on the ISS pass
self.showicon()
self.passingstatus = "ISS Zarya is above the horizon!"
self.curpass.get_child().set_text(self.passingstatus)
self.curpassdur.get_child().set_text("Duration: "+curdurationstr+" ("+curremainingtimeleftstr+" remaining)")
self.curpassdur.show()
self.curpassrise.get_child().set_text("Rise time: "+currisetime_tzstr)
self.curpassrise.show()
self.curpassset.get_child().set_text("Set time: "+cursettime_tzstr)
self.curpassset.show()
break
else:
#if this iteration of ISS passes does not match with current time, then increase self.n
self.n += 1
#regardless of results show the next pass time
#if the ISS is overhead, use the next dictionary key for data
if self.n != len(self.data['response']):
nextrisetime_utc = datetime.datetime.utcfromtimestamp(self.data['response'][self.n+1]['risetime'])
else:
#if the ISS is not overhead, use the first key in the dictionary
nextrisetime_utc = datetime.datetime.utcfromtimestamp(self.data['response'][0]['risetime'])
#calculate the next rise time and make timers
nextrisetime_utc = nextrisetime_utc.replace(tzinfo=tz.gettz('UTC'))
nextrisetime_tz = nextrisetime_utc.astimezone(tz.tzlocal())
remainingtimeleft = nextrisetime_utc - current_utc
z= remainingtimeleft.seconds
zhours = z/60/60
zminutes = z/60-zhours*60
zseconds = z-zhours*60*60-zminutes*60
remainingtimeleftstr = str(zhours)+':'+str("%02d" % (zminutes))+':'+str("%02d" % (zseconds))
nextrisetime_tzstr = str("%02d" % (nextrisetime_tz.hour))+':'+str("%02d" % (nextrisetime_tz.minute))+':'+str("%02d" % (nextrisetime_tz.second))
#update GTK menuitem
self.futpass.get_child().set_text("Next Pass: "+nextrisetime_tzstr+" ("+remainingtimeleftstr+")")
#if the ISS is not above the horizon, refresh GTK only once its time for the icon to be visible
if self.passingstatus != "ISS Zarya is above the horizon!":
self.setrefresh(remainingtimeleft.seconds*1000+100)
#self.setrefresh(1000)
self.curpass.get_child().set_text(self.passingstatus)
self.curpassdur.hide()
self.curpassrise.hide()
self.curpassset.hide()
if self.isiconhidden == True:
self.hideicon()
else:
#otherwise, refresh once a second to show the timers ticking in the menu
#test if the menu is active instead of always running like in this example
####MISSING CODE HERE##### DONT KNOW HOW TO DO IT, SO JUST SETTING TO 1 SEC
self.setrefresh(1000)
#for when setrefresh calls this function
return True
if __name__ == '__main__':
issindicator = indicator()

Reading the target of a .lnk file in Python?

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 spec is way over my head.
I don't mind using Windows-only APIs.
My ultimate goal is to find the "(My) Videos" folder on Windows XP and Vista. On XP, by default, it's at %HOMEPATH%\My Documents\My Videos, and on Vista it's %HOMEPATH%\Videos. However, the user can relocate this folder. In the case, the %HOMEPATH%\Videos folder ceases to exists and is replaced by %HOMEPATH%\Videos.lnk which points to the new "My Videos" folder. And I want its absolute location.
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)
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])
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
Basically call the Windows API directly. Here is a good example found after Googling:
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)
This is from http://timgolden.me.uk/python/win32_how_do_i/create-a-shortcut.html.
You can search for "python ishelllink" for other examples.
Also, the API reference helps too: http://msdn.microsoft.com/en-us/library/bb774950(VS.85).aspx
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
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", lnkFile.read(), flags=re.DOTALL)
# Close File
lnkFile.close()
# Read the pdf at the lnk Target
pdfFile = open(tmpFilePath[0], 'rb')
Comments:
Obviously this works for pdf but needs to specify other file extensions accordingly.
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:
The location should be within double inverted commas.
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.
this job is possible without any modules, doing this will return a b string having the destination of the shortcut file. Basically what you do is you open the file in read binary mode (rb mode). This is the code to accomplish this task:
with open('programs.lnk - Copy','rb') as f:
destination=f.read()
i am currently using python 3.9.2, in case you face problems with this, just tell me and i will try to fix it.
A more stable solution in python, using powershell to read the target path from the .lnk file.
using only standard libraries avoids introducing extra dependencies such as win32com
this approach works with the .lnks that failed with jared's answer, more details
we avoid directly reading the file, which felt hacky, and sometimes failed
import subprocess
def get_target(link_path) -> (str, str):
"""
Get the target & args of a Windows shortcut (.lnk)
:param link_path: The Path or string-path to the shortcut, e.g. "C:\\Users\\Public\\Desktop\\My Shortcut.lnk"
:return: A tuple of the target and arguments, e.g. ("C:\\Program Files\\My Program.exe", "--my-arg")
"""
# get_target implementation by hannes, https://gist.github.com/Winand/997ed38269e899eb561991a0c663fa49
ps_command = \
"$WSShell = New-Object -ComObject Wscript.Shell;" \
"$Shortcut = $WSShell.CreateShortcut(\"" + str(link_path) + "\"); " \
"Write-Host $Shortcut.TargetPath ';' $shortcut.Arguments "
output = subprocess.run(["powershell.exe", ps_command], capture_output=True)
raw = output.stdout.decode('utf-8')
launch_path, args = [x.strip() for x in raw.split(';', 1)]
return launch_path, args
# to test
shortcut_file = r"C:\Users\REPLACE_WITH_USERNAME\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Accessibility\Narrator.lnk"
a, args = get_target(shortcut_file)
print(a) # C:\WINDOWS\system32\narrator.exe
(you can remove -> typehinting to get it to work in older python versions)
I did notice this is slow when running on lots of shortcuts. You could use jareds method, check if the result is None, and if so, run this code to get the target path.
The nice approach with direct regex-based parsing (proposed in the answer) didn't work reliable for all shortcuts in my case. Some of them have only relative path like ..\\..\\..\\..\\..\\..\\Program Files\\ImageGlass\\ImageGlass.exe (produced by msi-installer), and it is stored with wide chars, which are tricky to handle in Python.
So I've discovered a Python module LnkParse3, which is easy to use and meets my needs.
Here is a sample script to show target of a lnk-file passed as first argument:
import LnkParse3
import sys
with open(sys.argv[1], 'rb') as indata:
lnk = LnkParse3.lnk_file(indata)
print(lnk.lnk_command)
I arrived at this thread looking for a way to parse a ".lnk" file and get the target file name.
I found another very simple solution:
pip install comtypes
Then
from comtypes.client import CreateObject
from comtypes.persist import IPersistFile
from comtypes.shelllink import ShellLink
# MAKE SURE THIS VAT CONTAINS A STRING AND NOT AN OBJECT OF 'PATH'
# I spent too much time figuring out the problem with .load(..) function ahead
pathStr="c:\folder\yourlink.lnk"
s = CreateObject(ShellLink)
p = s.QueryInterface(IPersistFile)
p.Load(pathStr, False)
print(s.GetPath())
print(s.GetArguments())
print(s.GetWorkingDirectory())
print(s.GetIconLocation())
try:
# the GetDescription create exception in some of the links
print(s.GetDescription())
except Exception as e:
print(e)
print(s.Hotkey)
print(s.ShowCmd)
Based on this great answer...
https://stackoverflow.com/a/43856809/2992810

Categories

Resources