tags:

views:

92

answers:

2

Hello everyone :)

I've got a series of filepaths that look something like this:

  • C:\Windows\System32\svchost.exe -k LocalSystemNetworkRestricted
  • C:\Windows\System32\svchost
  • C:\Program Files (x86)\Common Files\Steam\SteamService.exe /RunAsService
  • "C:\Program Files (x86)\Common Files\Steam\SteamService.exe" /RunAsService

and I need to find these paths' actual locations. So, respectively, the above would be:

  • C:\Windows\System32\svchost.exe
  • C:\Windows\System32\svchost.exe
  • C:\Program Files (x86)\Common Files\Steam\SteamService.exe
  • C:\Program Files (x86)\Common Files\Steam\SteamService.exe

What's the best way to go about doing this? Does windows have an API function to accomplish it? I essentially am trying to figure out what executable CreateProcess will call if I pass it that path.

Thanks!

Billy3

EDIT: This is the code I settled on for now:

#include <algorithm>
#include <vector>
#include <string>
#include <boost/algorithm/string.hpp>
#include <Windows.h>

namespace Path {

bool Exists(const std::wstring& path)
{
    DWORD result = GetFileAttributesW(path.c_str());
    return result != INVALID_FILE_ATTRIBUTES;
}

#define PATH_PREFIX_RESOLVE(path, prefix, environment) \
if (boost::algorithm::istarts_with(path, prefix)) { \
    ExpandEnvironmentStringsW(environment, buffer, MAX_PATH); \
    path.replace(0, (sizeof(prefix)/sizeof(wchar_t)) - 1, buffer); \
    if (Exists(path)) return path; \
}

std::wstring Resolve(std::wstring path)
{
    using namespace boost::algorithm;
    wchar_t buffer[MAX_PATH];
    trim(path);
    if (path.empty() || Exists(path)) return path;

    //Start by trying to see if we have a quoted path
    if (path[0] == L'"') {
        return std::wstring(path.begin() + 1, std::find(path.begin() + 1, path.end(), L'"'));
    }

    //Check for those nasty cases where the beginning of the path has no root
    PATH_PREFIX_RESOLVE(path, L"\\", L"");
    PATH_PREFIX_RESOLVE(path, L"?\?\\", L"");
    PATH_PREFIX_RESOLVE(path, L"\\?\\", L"");
    PATH_PREFIX_RESOLVE(path, L"globalroot\\", L"");
    PATH_PREFIX_RESOLVE(path, L"system32\\", L"%systemroot%\\System32\\");
    PATH_PREFIX_RESOLVE(path, L"systemroot\\", L"%systemroot%\\");

    static std::vector<std::wstring> pathExts;
    if (pathExts.empty()) {
        #define MAX_ENVVAR 32767
        wchar_t pathext[MAX_ENVVAR];
        DWORD length = GetEnvironmentVariableW(L"PATHEXT", pathext, MAX_ENVVAR);
        if (!length) WindowsApiException::ThrowFromLastError();
        split(pathExts, pathext, std::bind2nd(std::equal_to<wchar_t>(), L';'));
        pathExts.insert(pathExts.begin(), std::wstring());
    }
    std::wstring::iterator currentSpace = path.begin();
    do {
        currentSpace = std::find(currentSpace, path.end(), L' ');
        std::wstring currentPath(path.begin(), currentSpace);
        std::wstring::size_type currentPathLength = currentPath.size();
        typedef std::vector<std::wstring>::const_iterator ExtIteratorType;
        for(ExtIteratorType it = pathExts.begin(); it != pathExts.end(); it++) {
            currentPath.replace(currentPathLength, currentPath.size() - currentPathLength, *it);
            if (Exists(currentPath)) return currentPath;
        }
        if (currentSpace != path.end())
            currentSpace++;
    } while (currentSpace != path.end());

    return path;
}

}
+3  A: 

Number 4 should be relatively easy. If the path starts with a " character, just read until the next " and that's the path. With the others, it's slightly more tricky, but the way Windows does it is by simply breaking the command line into parts, and trying one at a time, so looking at #3, it breaks it up into an array like this:

["C:\Program", "Files", "(x86)\Common", "Files\Steam\SteamService.exe", "/RunAsService"]

Then it simply starts from the left-most element and looks for files:

  1. C:\Program
  2. C:\Program Files
  3. C:\Program Files (x86)\Common
  4. C:\Program Files (x86)\Common Files\Stream\StreamService.exe
  5. C:\Program Files (x86)\Common Files\Steam\SteamService.exe /RunAsService

Each step, it checks whether a file with that name exists. If so, that's the one it chooses. It also tries appending ".exe" to the name. So in the first step, it checks whether there is a file called "C:\Program.exe" and if so, that's the first. If not, it moves to the second step and tries "C:\Program Files.exe". If that doesn't exist, it moves to the next one and so on.

There have been issues in the past with how this algorithm works, for example, see here.

Dean Harding
This can't be entirely correct -- I believe CreateProcess is parsing the PATHEXT environment variable. If I create a run key (`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\\Runme = "C:\Program"`) and put an executable as C:\program.com, it is being executed at startup.
Billy ONeal
Yes, I believe you are correct. It will try C:\Program.exe, C:\Program.com, C:\Program.bat and so on - all the extenions from PATHEXT will be attempted.
Dean Harding
Looks like I have to write it myself grr... hope I don't f*** it up! :P
Billy ONeal
@codeka: Posted my code in case you might find it useful.
Billy ONeal
A: 

See the Shell Path Handling Functions in shlwapi.h. Your examples should make it with ::PathRemoveArgs(sPath) followed by ::PathMatchSpec(sPath, _T("*.exe")).

cheers, AR

Alain Rist
This will not work with case 3 in the question.
Dean Harding
Right he should also call ::PathUnquoteSpaces() :-)
Alain Rist
`PathUnquoteSpaces` also will not help with case 3...
Dean Harding
You're also assuming the file is an EXE file which is not the case. Plus, something like `C:\Windows\System32\svchost -k something` is a valid path which needs to return `C:\Windows\System32\svchost.exe` or `C:\Windows\System32\svchost.com` or whatever as specified by `%PATHEXT%`.
Billy ONeal