How to create a shortcut to a folder on Windows? - python

I have created shortcuts for executables and it works, but when I try to create one for a folder it does not work.
It does create a shortcut, it is just not the right 'Target Type'. Please take a look at the image below.
Instead of 'File', the target type should be 'File folder'. The problem is that when I open the shortcut it asks me which program do I want to open the File with and it does not open the folder.
The function I'm using to create the shortcuts is the following
from win32com.client import Dispatch
import winshell
import os
def create_shortcuts(self, tool_name, exe_path, startin, icon_path):
shell = Dispatch('WScript.Shell')
shortcut_file = os.path.join(winshell.desktop(), tool_name + '.lnk')
shortcut = shell.CreateShortCut(shortcut_file)
shortcut.Targetpath = exe_path
shortcut.WorkingDirectory = startin
shortcut.IconLocation = icon_path
shortcut.save()
I don't know if it's possible to set the 'Target Type'. I couldn't find a way to do it, but I do know there must be a way.

If you want to use .Net "clr" (especially if you already require it):
First run this... you will have to ship the output of this command with your application:
"c:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\TlbImp.exe" %SystemRoot%\system32\wshom.ocx /out:Interop.IWshRuntimeLibrary.dll
tlbimp.exe might even be in the path if you installed the Windows SDK in a fairly standard way. But if not, it's OK, you'll just ship the "assembly" (fancy word for interface-providing dll in .Net land) with your application.
Then this code will work in python:
import clr
sys.path.append(DIRECTORY_WHERE_YOU_PUT_THE_DLL)
clr.AddReference('Interop.IWshRuntimeLibrary')
import Interop.IWshRuntimeLibrary
sc = Interop.IWshRuntimeLibrary.WshShell().CreateShortcut("c:\\test\\sc.lnk")
isc = Interop.IWshRuntimeLibrary.IWshShortcut(sc)
isc.set_TargetPath("C:\\")
isc.Save()
.... the above code, with far too much modification and preamble, might even work with Mono.

For future reference: I observed the described behavior in python 3.9.6 when creating a shortcut to a non-existing directory, which was easily fixed by incorporating os.makedirs() into the method.
I've added a method parameter to the version I'm using, so it can handle shortcuts to files and directories:
def create_shortcuts(self, tool_name, exe_path, startin, icon_path, is_directory=False):
if is_directory:
os.makedirs(exe_path, exist_ok=True)
shell = Dispatch('WScript.Shell')
shortcut_file = os.path.join(winshell.desktop(), tool_name + '.lnk')
shortcut = shell.CreateShortCut(shortcut_file)
shortcut.Targetpath = exe_path
shortcut.WorkingDirectory = startin
shortcut.IconLocation = icon_path
shortcut.save()

Related

How to allow game players to upload images to pygame game? [duplicate]

I have a simple script which parses a file and loads it's contents to a database. I don't need a UI, but right now I'm prompting the user for the file to parse using raw_input which is most unfriendly, especially because the user can't copy/paste the path. I would like a quick and easy way to present a file selection dialog to the user, they can select the file, and then it's loaded to the database. (In my use case, if they happened to chose the wrong file, it would fail parsing, and wouldn't be a problem even if it was loaded to the database.)
import tkFileDialog
file_path_string = tkFileDialog.askopenfilename()
This code is close to what I want, but it leaves an annoying empty frame open (which isn't able to be closed, probably because I haven't registered a close event handler).
I don't have to use tkInter, but since it's in the Python standard library it's a good candidate for quickest and easiest solution.
Whats a quick and easy way to prompt for a file or filename in a script without any other UI?
Tkinter is the easiest way if you don't want to have any other dependencies.
To show only the dialog without any other GUI elements, you have to hide the root window using the withdraw method:
import tkinter as tk
from tkinter import filedialog
root = tk.Tk()
root.withdraw()
file_path = filedialog.askopenfilename()
Python 2 variant:
import Tkinter, tkFileDialog
root = Tkinter.Tk()
root.withdraw()
file_path = tkFileDialog.askopenfilename()
You can use easygui:
import easygui
path = easygui.fileopenbox()
To install easygui, you can use pip:
pip3 install easygui
It is a single pure Python module (easygui.py) that uses tkinter.
Try with wxPython:
import wx
def get_path(wildcard):
app = wx.App(None)
style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
dialog = wx.FileDialog(None, 'Open', wildcard=wildcard, style=style)
if dialog.ShowModal() == wx.ID_OK:
path = dialog.GetPath()
else:
path = None
dialog.Destroy()
return path
print get_path('*.txt')
pywin32 provides access to the GetOpenFileName win32 function. From the example
import win32gui, win32con, os
filter='Python Scripts\0*.py;*.pyw;*.pys\0Text files\0*.txt\0'
customfilter='Other file types\0*.*\0'
fname, customfilter, flags=win32gui.GetOpenFileNameW(
InitialDir=os.environ['temp'],
Flags=win32con.OFN_ALLOWMULTISELECT|win32con.OFN_EXPLORER,
File='somefilename', DefExt='py',
Title='GetOpenFileNameW',
Filter=filter,
CustomFilter=customfilter,
FilterIndex=0)
print 'open file names:', repr(fname)
print 'filter used:', repr(customfilter)
print 'Flags:', flags
for k,v in win32con.__dict__.items():
if k.startswith('OFN_') and flags & v:
print '\t'+k
Using tkinter (python 2) or Tkinter (python 3) it's indeed possible to display file open dialog (See other answers here). Please notice however that user interface of that dialog is outdated and does not corresponds to newer file open dialogs available in Windows 10.
Moreover - if you're looking on way to embedd python support into your own application - you will find out soon that tkinter library is not open source code and even more - it is commercial library.
(For example search for "activetcl pricing" will lead you to this web page: https://reviews.financesonline.com/p/activetcl/)
So tkinter library will cost money for any application wanting to embedd python.
I by myself managed to find pythonnet library:
Overview here: http://pythonnet.github.io/
Source code here: https://github.com/pythonnet/pythonnet
(MIT License)
Using following command it's possible to install pythonnet:
pip3 install pythonnet
And here you can find out working example for using open file dialog:
https://stackoverflow.com/a/50446803/2338477
Let me copy an example also here:
import sys
import ctypes
co_initialize = ctypes.windll.ole32.CoInitialize
# Force STA mode
co_initialize(None)
import clr
clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import OpenFileDialog
file_dialog = OpenFileDialog()
ret = file_dialog.ShowDialog()
if ret != 1:
print("Cancelled")
sys.exit()
print(file_dialog.FileName)
If you also miss more complex user interface - see Demo folder
in pythonnet git.
I'm not sure about portability to other OS's, haven't tried, but .net 5 is planned to be ported to multiple OS's (Search ".net 5 platforms", https://devblogs.microsoft.com/dotnet/introducing-net-5/ ) - so this technology is also future proof.
If you don't need the UI or expect the program to run in a CLI, you could parse the filepath as an argument. This would allow you to use the autocomplete feature of your CLI to quickly find the file you need.
This would probably only be handy if the script is non-interactive besides the filepath input.
Another os-agnostic option, use pywebview:
import webview
def webview_file_dialog():
file = None
def open_file_dialog(w):
nonlocal file
try:
file = w.create_file_dialog(webview.OPEN_DIALOG)[0]
except TypeError:
pass # user exited file dialog without picking
finally:
w.destroy()
window = webview.create_window("", hidden=True)
webview.start(open_file_dialog, window)
# file will either be a string or None
return file
print(webview_file_dialog())
Environment: python3.8.6 on Mac - though I've used pywebview on windows 10 before.
I just stumbled on this little trick for Windows only: run powershell.exe from subprocess.
import subprocess
sys_const = ssfDESKTOP # Starts at the top level
# sys_const = 0x2a # Correct value for "Program Files (0x86)" folder
powershell_browse = "(new-object -COM 'Shell.Application')."
powershell_browse += "BrowseForFolder(0,'window title here',0,sys_const).self.path"
ret = subprocess.run(["powershell.exe",powershell_browse], stdout=subprocess.PIPE)
print(ret.stdout.decode())
Note the optional use of system folder constants. (There's an obscure typo in shldisp.h that the "Program Files (0x86)" constant was assigned wrong. I added a comment with the correct value. Took me a bit to figure that one out.)
More info below:
System folder constants

How can I target files and folders in Mac OS the same way I target them in Windows despite having unique usernames? [duplicate]

I need to get the location of the home directory of the current logged-on user. Currently, I've been using the following on Linux:
os.getenv("HOME")
However, this does not work on Windows. What is the correct cross-platform way to do this ?
You want to use os.path.expanduser.
This will ensure it works on all platforms:
from os.path import expanduser
home = expanduser("~")
If you're on Python 3.5+ you can use pathlib.Path.home():
from pathlib import Path
home = str(Path.home())
I found that pathlib module also supports this.
from pathlib import Path
>>> Path.home()
WindowsPath('C:/Users/XXX')
I know this is an old thread, but I recently needed this for a large scale project (Python 3.8). It had to work on any mainstream OS, so therefore I went with the solution #Max wrote in the comments.
Code:
import os
print(os.path.expanduser("~"))
Output Windows:
PS C:\Python> & C:/Python38/python.exe c:/Python/test.py
C:\Users\mXXXXX
Output Linux (Ubuntu):
rxxx#xx:/mnt/c/Python$ python3 test.py
/home/rxxx
I also tested it on Python 2.7.17 and that works too.
This can be done using pathlib, which is part of the standard library, and treats paths as objects with methods, instead of strings.
Path.expanduser()
Path.home()
from pathlib import Path
home: str = str(Path('~').expanduser())
This doesn't really qualify for the question (it being tagged as cross-platform), but perhaps this could be useful for someone.
How to get the home directory for effective user (Linux specific).
Let's imagine that you are writing an installer script or some other solution that requires you to perform certain actions under certain local users. You would most likely accomplish this in your installer script by changing the effective user, but os.path.expanduser("~") will still return /root.
The argument needs to have the desired user name:
os.path.expanduser(f"~{USERNAME}/")
Note that the above works fine without changing EUID, but if the scenario previously described would apply, the example below shows how this could be used:
import os
import pwd
import grp
class Identity():
def __init__(self, user: str, group: str = None):
self.uid = pwd.getpwnam(user).pw_uid
if not group:
self.gid = pwd.getpwnam(user).pw_gid
else:
self.gid = grp.getgrnam(group).gr_gid
def __enter__(self):
self.original_uid = os.getuid()
self.original_gid = os.getgid()
os.setegid(self.uid)
os.seteuid(self.gid)
def __exit__(self, type, value, traceback):
os.seteuid(self.original_uid)
os.setegid(self.original_gid)
if __name__ == '__main__':
with Identity("hedy", "lamarr"):
homedir = os.path.expanduser(f"~{pwd.getpwuid(os.geteuid())[0]}/")
with open(os.path.join(homedir, "install.log"), "w") as file:
file.write("Your home directory contents have been altered")

Creating an executable python script, with OS commands, to start Blender and run another script

I need a little help creating an executable python OS command for Blender (Windows and Mac). I am setting up a stand-alone blender package for a client to use. I have a python script that imports some data and I want to create an executable python script that runs OS commands to run Blender, run the python script and define the data directories.
This is a generic version of the Windows command I normally run to import the data, using the stand-alone version of Blender:
"C:\Users\username\Desktop\Package\system\blender\blender.exe" "C:\Users\username\Desktop\Package\system\version_data\CurrentVersion.blend" -P "C:\Users\username\Desktop\Package\system\version_data\BlenderScript.py" "C:\Users\username\Desktop\Package\input_data\\" -y
From my research I have worked out that I need to:
import os
make the directories in the command relative
create an executable python file
My python experience is limited, but I believe it may be something like this:
import os
pythonDirectory = os.path.dirname(os.path.abspath(RunThisApp.exe)) # get the current dir of this file (which would be: C:\Users\username\Desktop\Package\)
os.path.join(pathDirectory, "//system\blender\blender.exe" "//system\version_data\CurrentVersion.blend" -P "//system\version_data\BlenderScript.py" "//input_data\\" -y)
However I had a look at this post and was a little fuzzy as to the best way to do this:
Calling an external command in Python
Then I could possibly use PyInstaller to create the python executable files. Which seems to be the simplest method suggested here:
How to make a Python script standalone executable to run without ANY dependency?
http://www.pyinstaller.org/
Am I close to the correct result here? I am guessing my syntax is off. I need to make sure it works for both Windows and Mac.
It should also be noted that the separate python script I run to import data into blender (which I have been using and updating for a couple of years), refers to OS arguments to get the desired path of data to import, so I need to make sure that I maintain that connection. Here is an example:
IMPORT_DATA_FILENAME = sys.argv[4]+'data.txt'
Any advice or input would be greatly appreciated.
Chris Lee
It looks like you have all the files and binaries you want to use included in one folder that you are sharing. If you place the initial script at the top of that directory you should be able to get it's location from argv[0] and calculate the location of the other files you want from that, which should allow the user to move your package folder anywhere they want.
You can use platform.system() to add any system specific variations to the paths.
#!/usr/bin/env python3
import os
from sys import argv
from platform import system
from subprocess import call
# define the package dir as the dir that this script is in
pkg_dir = os.path.abspath(os.path.dirname(argv[0]))
# use a package dir in the users home folder
#pkg_dir = os.path.join(os.path.expanduser('~'), 'Package')
# use a package dir in the users desktop folder
#pkg_dir = os.path.join(os.path.expanduser('~'), 'Desktop', 'Package')
sys_dir = os.path.join(pkg_dir, system())
vers_dir = os.path.join(pkg_dir,'version_data')
blend = os.path.join(vers_dir, 'CurrentVersion.blend')
script = os.path.join(vers_dir, 'BlenderScript.py')
if system() == 'Windows':
blender_exe = os.path.join(sys_dir, 'blender', 'blender.exe')
elif system() == 'Darwin':
blender_exe = os.path.join(sys_dir, 'blender', 'blender.app',
'Contents', 'MacOS', 'blender')
else:
# linux?
blender_exe = os.path.join(sys_dir, 'blender', 'blender')
calllist = [
blender_exe,
blend,
'--python',
script,
]
call(calllist)
Using Sambler's solution I have modified it slightly to got the following solution:
import os
from sys import argv
from platform import system
from subprocess import call
# define the package dir as the dir that this script is in
pkg_dir = os.path.abspath(os.path.dirname(argv[0]))
sys_dir = os.path.join(pkg_dir, 'private_sys', system())
vers_dir = os.path.join(pkg_dir, 'private_sys', '#version_data')
blend = os.path.join(vers_dir, 'CurrentVersion.blend')
script = os.path.join(vers_dir, 'BlenderScript.py')
input = os.path.join(pkg_dir, 'input_data')
if system() == 'Windows':
blender_exe = os.path.join(sys_dir, 'blender', 'blender.exe')
elif system() == 'Darwin':
blender_exe = os.path.join(sys_dir, 'blender', 'blender.app', 'Contents', 'MacOS', 'blender')
calllist = [
blender_exe,
blend,
'--python',
script,
input,
]
call(calllist)
To clarify, within the Package directory the folder structure is as follows:
+---input_data
\---data.txt
\---input.blend
\---private_sys
+---#version_data
\---BlenderScript.py
\---CurrentVersion.blend
+---Darwin
| \---blender
\---Windows
\---blender
Apart from removing the Linux and other suggested variations, I added an input directory. The BlenderScript.py that is called refers to the data.txt and the input.blend files and imports them into the CurrentVersion.blend file. I also moved the version data into a separate directory from the OS specific blender directories and inside the private_sys directory, so that the user doesn't have to see those files.
The last hurdle was lines like this inside BlenderScript.py:
IMPORT_DATA_FILENAME = sys.argv[4]+'data.txt'
The problem was that I was getting an error because the script would end up looking for: "\input_datadata.txt".
I fixed this by changing it to:
IMPORT_DATA_FILENAME = sys.argv[4]+'/data.txt'
Which returns: "\input_data/data.txt". I am assuming this will have the same result in OSX.
Thank you very much for your help. This is an area I am learning more in all the time, as the work I do for the client is far more complicated than 'past me' could have imagined. 'Future me' is going to be very pleased with himself when all this is done.

How do I get Windows to remember the last directory used using tkinter filedialog?

Having code like:
from tkinter import filedialog
image_formats= [("JPEG", "*.jpg"), ("All files", "*.*")]
file=filedialog.askopenfilename(filetypes=image_formats)
I can open a file dialog box which leads me to a .jpg file.
On my Windows 7 development box this remembers over closing and opening the program the directory -- easy allowing selecting multiple files from the directory.
However, after distribution, using cx_Freeze and its bdist_msi option, the same program to a Windows 10 machine the directory is no longer remembered. How do I get the Windows 7 behaviour on the Windows 10 system? Preferably I do not perform this manually but rely on the underlying Windows mechanism.
PS Full bdist_msi distribution of the actual program is available at https://sites.google.com/site/klamerschutte/system/app/pages/admin/revisions?wuid=wuid:gx:403dfb9983518218
I know this is an old question but I happened to run into this issue recently. I tried this with the latest version of Python 3.7.
My way around this: Just don't add an initialdir argument. On Windows 10, it will start from the last used directory when the filedialog is called.
If I understand your question properly you want to know how to set the initial starting directory whenever the dialog is shown for selecting a file (of whatever types).
You may set this by the initialdir argument to askopenfilename which will take the path of the starting directory you want.
For example if I always wanted the dialog to open to the user's home folder then I could use os.path.expanduser('~') as the initialdir argument.
If you want it to remember the last directory used then get the parent directory of the file selected from the dialog using os.pardir and store it in a variable.
Try below code, it will remember the last directory used by the tool
filename = askopenfilename(parent=root,title='Choose template file', filetypes =[('Excel Files', '*.xlsx')])
Try this:
CORRECTION:
create a pages_to_connect_pages.py to allow variables go inside any class (from stackoverflow, I don´t remember the reference, sorry)
2.create a default directory in never_opened_directory.txt, to use first time
last_opened_directory.txt is created when you open a directory
use their references in pages_to_connect_pages.py
insert references in main app as bellow
[![create this file and all variables go to any class using Pages.something (took from stackoverflow, dont remember where from) ][1]][1]
[![create files to "remember" last directory opened after main app is closed][2]][2]
in main app:
my_app
from pages_to_connect_pages import Pages
# we see if the file last_opened_directory.txt is empty(never used)
filesize = os.path.getsize("last_opened_directory.txt")
# if Pages.last_opened_directory == '':
if filesize == 0:
Pages.never_opened_directory = open("never_opened_directory.txt", "r")
directory_to_open = Pages.never_opened_directory.read()
Pages.never_opened_directory.close()
# elif Pages.last_opened_directory != '':
elif filesize != 0:
Pages.last_opened_directory = open("last_opened_directory.txt", "r")
directory_to_open = Pages.last_opened_directory.read()
Pages.last_opened_directory.close()
else:
pass
reference:Python save variable even after closing the gui
If you want askopenfilename to remember last used directory, set initialdir to a nonexistent folder, something like initialdir = "/áéá".
This works for me on Windows 10.

Adding a single python executable to windows system PATH for multiple computers?

I've created a command line program that I would like to distribute to some folks at work. Getting them all to install the python interpreter is simply unrealistic. Hence, I've created a single .exe file using PyInstaller. I am coming to realize however, that most people don't even know how to navigate to the directory where the .exe is, in order to invoke it. (And as of now, I haven't yet figured out how to get the program to run when clicked.) Is there a way to make the program add it's self to the users sys PATH when it is run the first time or would this require an installer? Thanks!
The common trap would be to read the PATH env. variable by using os.environ('PATH'). That would be a big mistake because this variable contains user & system paths mixed together. That's a special case for the PATH variable.
What you need to do is to fetch PATH env variable from the registry (user part), update it if needed, and write it back.
You can achieve that using winreg module, modifying the user PATH environment variable (or create if doesn't exist for this particular user)
read user PATH variable
if exists, tokenize the paths (else, path list defaults to empty)
compute the path of the current module (using os.path.dirname(__file__))
check if already in the path, if so, exit (I print the path list in that case so you can test)
create/update PATH user env. variable with the updated path list if necessary
Code:
import winreg,os
script_directory = os.path.dirname(__file__)
paths = []
key_type = winreg.REG_EXPAND_SZ # default if PATH doesn't exist
try:
keyQ = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_QUERY_VALUE)
path_old, key_type = winreg.QueryValueEx(keyQ, "PATH")
winreg.CloseKey(keyQ)
paths = path_old.split(os.pathsep)
except WindowsError:
pass
if script_directory in paths:
# already set, do nothing
print(paths)
else:
# add the new path
paths.append(script_directory)
# change registry
keyQ = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_WRITE)
winreg.SetValueEx(keyQ, 'PATH', 0, key_type, os.pathsep.join(paths))
winreg.CloseKey(keyQ)
Note that the user will have to logoff/logon for changes to take effect. Another solution would be to call setx on the PATH variable. System call, ugly, but effective immediately.
# change registry with immediate effect
import subprocess
subprocess.call(["setx","PATH",os.pathsep.join(paths)])
Or, courtesy to eryksun, some python code to propagate the registry changes to new processes. No need to logoff, no need for ugly setx, just call broadcast_change('Environment') using the code below:
import ctypes
user32 = ctypes.WinDLL('user32', use_last_error=True)
HWND_BROADCAST = 0xFFFF
WM_SETTINGCHANGE = 0x001A
SMTO_ABORTIFHUNG = 0x0002
ERROR_TIMEOUT = 0x05B4
def broadcast_change(lparam):
result = user32.SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE,
0, ctypes.c_wchar_p(lparam), SMTO_ABORTIFHUNG, 1000, None)
if not result:
err = ctypes.get_last_error()
if err != ERROR_TIMEOUT:
raise ctypes.WinError(err)
(seems that I have to refactor some code of my own with that last bit :))
env. variable read code took from here: How to return only user Path in environment variables without access to Registry?

Categories