This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author eryksun
Recipients barneygale, eryksun, serhiy.storchaka
Date 2021-04-08.15:50:33
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1617897033.63.0.0234053907199.issue42998@roundup.psfhosted.org>
In-reply-to
Content
> 3. Worth fixing in `ntpath.expanduser()` 

To expand on the suggestion in msg390507, here's a demo that implements getting the profile path of a local user account, with support for the special profile directories "Default", "Public", and "ProgramData" (i.e. ALLUSERSPROFILE).

    ERROR_NONE_MAPPED = 1332

    def get_profile_path(name):
        if not isinstance(name, str):
            raise TypeError(f'name must be str, not {type(name).__name__}')
        profile_list = r'Software\Microsoft\Windows NT\CurrentVersion\ProfileList'
        if name.lower() in ('default', 'public', 'programdata'):
            subkey = profile_list
            value = name
        else:
            try:
                sid = lookup_account_name(name)[0]
            except OSError as e:
                if e.winerror != ERROR_NONE_MAPPED:
                    raise
                raise KeyError(f'name not found: {name}')
            subkey = f'{profile_list}\\{sid}'
            value = 'ProfileImagePath'
        try:
            with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, subkey) as hkey:
                path, dtype = winreg.QueryValueEx(hkey, value)
        except FileNotFoundError:
            raise KeyError(f'name not found: {name}')
        if dtype == winreg.REG_SZ:
            return path
        if dtype == winreg.REG_EXPAND_SZ:
            return winreg.ExpandEnvironmentStrings(path)
        raise TypeError('profile path value must be a string type (1 or 2), '
                         f'not {dtype}')

For example:

    >>> print(get_profile_path('administrator'))
    C:\Users\Administrator

    >>> print(get_profile_path('default'))
    C:\Users\Default

    >>> print(get_profile_path('public'))
    C:\Users\Public

    >>> print(get_profile_path('programdata'))
    C:\ProgramData

    >>> print(get_profile_path('system'))
    C:\Windows\system32\config\systemprofile

    >>> print(get_profile_path('localservice'))
    C:\Windows\ServiceProfiles\LocalService

    >>> print(get_profile_path('networkservice'))
    C:\Windows\ServiceProfiles\NetworkService

For lookup_account_name(), _winapi.LookupAccountName(system_name, account_name) has to be implemented. It should convert the SID result to string form via ConvertSidToStringSidW() and return the tuple (sid_string, domain_name, account_type). Here's a ctypes prototype implementation of lookup_account_name():

    import ctypes

    lsalookup = ctypes.WinDLL(
        'api-ms-win-security-lsalookup-l2-1-0', use_last_error=True)
    sddl = ctypes.WinDLL('api-ms-win-security-sddl-l1-1-0', use_last_error=True)
    heap = ctypes.WinDLL('api-ms-win-core-heap-l2-1-0', use_last_error=True)

    ERROR_INSUFFICIENT_BUFFER = 122

    def lookup_account_name(name):
        sid = (ctypes.c_char * 1)()
        cb = ctypes.c_ulong()
        cch = ctypes.c_ulong()
        name_use = ctypes.c_ulong()
        lsalookup.LookupAccountNameW(None, name, sid, ctypes.byref(cb),
            None, ctypes.byref(cch), ctypes.byref(name_use))
        error = ctypes.get_last_error()
        if error != ERROR_INSUFFICIENT_BUFFER:
            raise ctypes.WinError(error)

        sid = (ctypes.c_char * cb.value)()
        domain_name = (ctypes.c_wchar * cch.value)()
        success = lsalookup.LookupAccountNameW(None, name, sid,
                        ctypes.byref(cb), domain_name, ctypes.byref(cch),
                        ctypes.byref(name_use))
        if not success:
            raise ctypes.WinError(ctypes.get_last_error())

        ssid = ctypes.c_wchar_p()
        if not sddl.ConvertSidToStringSidW(sid, ctypes.byref(ssid)):
            raise ctypes.WinError(ctypes.get_last_error())
        string_sid = ssid.value
        heap.LocalFree(ssid)

        return string_sid, domain_name.value, name_use.value
History
Date User Action Args
2021-04-08 15:50:33eryksunsetrecipients: + eryksun, serhiy.storchaka, barneygale
2021-04-08 15:50:33eryksunsetmessageid: <1617897033.63.0.0234053907199.issue42998@roundup.psfhosted.org>
2021-04-08 15:50:33eryksunlinkissue42998 messages
2021-04-08 15:50:33eryksuncreate