23

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.

xaav
  • 7,876
  • 9
  • 30
  • 47

4 Answers4

29

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.

The group policy editor

Note: Code snippet from here

Community
  • 1
  • 1
Fernando Macedo
  • 2,518
  • 1
  • 24
  • 25
  • I keep getting permission error, despite priviledges being set to my best knowledge correctly (the user is an administrator, the script runs in non-elevated mode). What could be the cause? – eudoxos Nov 06 '12 at 20:16
  • @eudoxos your python process need to be started with admin privileges. This can be done running it from an elevated cmd.exe. – Fernando Macedo Nov 21 '12 at 13:11
  • 1
    I've already found this out somewhere on the web. It is totally stupid, that the an administrator must launch its own process elevated to get it working. Since it is a user program, I cannot request users to run it in elevated mode. Security design made in microsoft. – eudoxos Nov 22 '12 at 09:47
  • 4
    For some obscure (at least to me) security reason, Microsoft enginneer decided to allow creation of symlinks just to members of 'Administrators' group. It is possible to change this with using "Local Security Policy" tool ('Create symbolic links' option). Unfortunately this does not work for users in Administrators group due to UAC: http://social.technet.microsoft.com/Forums/en-US/itprovistasecurity/thread/cb593ad0-9edc-4cd1-bb67-46c360b45f91 – Gian Marco Feb 23 '13 at 17:57
21

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()
Gian Marco
  • 22,140
  • 8
  • 55
  • 44
11

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
Community
  • 1
  • 1
Adobe
  • 12,967
  • 10
  • 85
  • 126
  • I understand that the function is designed to be a noop if a permissions problem is encountered, but I do not understand why the ctypes.WinError exception is raised at all if it is always going to be swallowed by the exception handler? Is this a placeholder to enable a developer to choose to do something different with the exception, or am I missing a subtlety here? – William Payne Aug 16 '16 at 15:27
  • @WilliamPayne: as far as I remember, it was making links given it has sufficient permissions, but would raise `WinError` in case `csl(link_name, source.replace('/', '\\'), flags) == 0` -- in which case it would also make a link. So everything worked, and I didn't understand why the `WinError`. So I just suppressed the error. – Adobe Aug 16 '16 at 15:43
0

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
bovesan
  • 61
  • 7