【问题标题】:SHGetKnownFolderPath fails with E_ACCESSDENIEDSHGetKnownFolderPath 失败并显示 E_ACCESSDENIED
【发布时间】:2016-05-30 15:06:24
【问题描述】:

我有一个使用 FOLDERID_RoamingAppData 调用 SHGetKnownFolderPath 的程序。

如果我通过双击启动程序,它可以正常工作。

如果程序由 Windows 服务启动(在当前用户上下文中),该函数将失败并出现错误 E_ACCESSDENIED (-2147024891)。

这是我的代码的样子:

Tstring EasyGetFolderPath(REFKNOWNFOLDERID folderid)
{
    Tstring sPath = _T("");
    PWSTR pszPath = NULL;

    HRESULT hr = SHGetKnownFolderPath(folderid, 0, NULL, &pszPath);

    if (hr == S_OK && pszPath)
    {
        sPath = WStringToTCHAR(pszPath);
        CoTaskMemFree(pszPath);
        return sPath;
    }
    else
    {
        throw HResultException(hr, _T("SHGetKnownFolderPath failed"));
    }
}

Tstring EasyGetUsrAppDataPath()
{
    return EasyGetFolderPath(FOLDERID_RoamingAppData);
}

static TCHAR* WStringToTCHAR(const std::wstring &s)
{
#ifdef UNICODE
    TCHAR *sT = new TCHAR[s.length() + 1];
    _tcscpy_s(sT, s.length() + 1, s.c_str());
    return sT;
#else
    std::string str = WStringToString(s);
    TCHAR *sT = new TCHAR[str.length()+1];
    _tcscpy_s(sT, str.length() + 1, str.c_str());
    return sT;
#endif // UNICODE
}

static std::string WStringToString(const std::wstring& s, bool method = true)
{
    std::string temp;
    temp.assign(s.begin(), s.end());
    return temp;
}

这是在当前用户上下文中启动进程的代码: (为了减少冗长,我删除了错误处理)

void StartProcessInCurrentUserContext(const Tstring &sExeName, const Tstringarr &lstParams, const Tstring &sWorkingDir)
{
    ...

    EnableDebugPrivilege();

    errCode = GetProcessByName(_T("explorer.exe"), hProcess);

    if (!OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken))
    {
        ...
    }

    if (hProcess)
        CloseHandle(hProcess);

    Tstring sCmdLine = ...;

    ...

    // Create the child process. 
    bSuccess = CreateProcessAsUser(hToken, NULL,
        (LPTSTR)sCmdLine.c_str(),            // command line 
        NULL,          // process security attributes 
        NULL,          // primary thread security attributes 
        TRUE,          // handles are inherited 
        0,             // creation flags 
        NULL,          // use parent's environment 
        sWorkingDir.length() > 0 ? (LPCTSTR)sWorkingDir.c_str() : NULL,
        &siStartInfo,  // STARTUPINFO pointer 
        &piProcInfo);  // receives PROCESS_INFORMATION 

    CloseHandle(hToken);

    ...
}

有人知道问题出在哪里吗?

【问题讨论】:

  • @conectionist 如果您使用std::vector<TCHAR> 而不是new TCHAR[],则可以避免内存泄漏。
  • 您说您“作为当前用户”从服务启动可执行文件。您究竟是如何做到这一点的(最好显示相关的CreateProcess(WithLogon) 代码)。 IOW 可能启动的进程没有像您认为的那样运行(例如,没有LOGON_WITH_PROFILE - 我可以想象这是一个潜在的问题)。
  • "CreateProcessAsUser 不会将指定用户的配置文件加载到 HKEY_USERS 注册表项中。"您需要加载配置文件才能访问用户配置文件状态。
  • @conectionist -- 这一行是高度可疑的:(LPTSTR)sCmdLine.c_str(),。如果你去掉(LPTSTR) 演员表,你会得到编译器错误吗?如果是这样,那么您的代码不正确。您不会通过投射将窄弦变成宽弦,反之亦然。这可以追溯到 Ulrich 提到的和 TCHAR 问题。
  • 您应该使用WTSQueryUserToken() 而不是GetProcessByName()/OpenProcessToken()。并且lpEnvironment参数也要使用CreateEnvironmentBlock(),不要继承服务的环境。

标签: c++ winapi known-folders


【解决方案1】:

The documentation for SHGetKnownFolderPathhToken参数的讨论中说:

除了传递用户的 hToken 之外,还必须挂载该特定用户的注册表配置单元。

The documentation for CreateProcessAsUser

CreateProcessAsUser 不会将指定用户的配置文件加载到 HKEY_USERS 注册表项中。

这两段一起解释了为什么您的代码不起作用。幸运的是,CreateProcessAsUser 文档中的下一句解释了您需要做什么:

因此,要访问 HKEY_CURRENT_USER 注册表项中的信息,您必须先使用 LoadUserProfile 函数将用户的配置文件信息加载到 HKEY_USERS 中调用 CreateProcessAsUser。请务必在新进程退出后调用 UnloadUserProfile

【讨论】:

  • 我不认为这可能是发布代码的问题 - 令牌取自 explorer.exe 的副本,因此应该已经加载了用户的个人资料。
  • @HarryJohnston 有两个问题。一是没有将错误的令牌作为 hToken 参数传递给 SHGetKnownFolderPath。另一个(显然与这里无关,但总体上是相关的)正在加载配置文件。
  • 默认是使用当前用户的token,所以那部分也应该没问题。但也许进程的安全权限不正确,因为它是从不同的上下文创建的,所以当 SHGetKnownFolderPath 去获取当前令牌时它会失败?有点像blogs.msdn.microsoft.com/oldnewthing/20160512-00/?p=93447
  • @HarryJohnston 哎呀,我看错了。我以为 SHGetKnownFolderPath 调用在服务中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-02
  • 2015-12-31
  • 2021-12-10
  • 2020-02-08
  • 2011-05-21
  • 2012-05-04
  • 2019-07-21
相关资源
最近更新 更多