Search code examples
c++windowswinapiimpersonationlnk

Problems with some system default .lnk-files launching from under an impersonated user


I'm writing the 32bit service app where I want to be able to launch Start menu items for the logged users. I did manage to accomplish this task by impersonating user and launching selected .lnk-file using CreateProcessAsUser with command-line: %windir%\system32\cmd /c " start /b /i "" "<path-to-lnk-file>" ". And it works for almost every shortcut except bunch of the system shortcuts from Accessories folder (e.g. Sticky Notes.lnk, Snipping Tool.lnk). During the launch of the Snipping Tool I'm receiving the message box with this error from cmd:

Windows cannot find 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\Snipping Tool.lnk'. Make sure you typed the name correctly, and then try again.

But the .lnk-file exists in this very directory!

Summary:

  • service is 32 bit
  • Windows 8 Pro x64
  • launching shortcuts by user impersonation and CreateProcessAsUser with command-line %windir%\system32\cmd /c " start /b /i "" "<path-to-lnk-file>" "
  • approach works for almost every shortcut in Start menu except some in Start/Accessories folder (not all of them, e.g. Paint.lnk opens fine)
  • example code:

    int launchAppForCurrentLoggedUser() 
    {
        HANDLE userToken = WTSApiHelper::currentLoggedUserToken();
        if (userToken == INVALID_HANDLE_VALUE) {
            return -1;
        }
    
        //Duplicating token with access TOKEN_DUPLICATE | TOKEN_ALL_ACCESS, 
        //impersonation level SecurityImpersonation and token type TokenPrimary.
        //Also closing original userToken
        HANDLE dup = WTSApiHelper::duplicateToken(userToken);
        if (dup == INVALID_HANDLE_VALUE) {
            return -1;
        }
    
        int res = -1;
    
        uint8 *env = NULL;
        BOOL succeeded = CreateEnvironmentBlock((LPVOID *)&env, dup, FALSE);
        if (!succeeded) {
            Log("failed to get environment variables for user (error 0x%x).", GetLastError());
        }
    
        PROCESS_INFORMATION pi;
        memset(&pi, 0, sizeof(PROCESS_INFORMATION));
    
        STARTUPINFOW si;
        memset(&si, 0, sizeof(STARTUPINFOW));
        si.cb = sizeof(STARTUPINFOW);
        si.lpDesktop = L"winsta0\\Default";
        WCHAR params[] = L"/c \" start /b /i \"\" \"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Accessories\\Snipping Tool.lnk\" \" ";
        WCHAR cmd[] = L"C:\\Windows\\system32\\cmd.exe";
    
        DWORD flags = env ? CREATE_UNICODE_ENVIRONMENT : 0;
        succeeded = CreateProcessAsUserW(dup, cmd, params, NULL, NULL, FALSE, flags | CREATE_NO_WINDOW, env, NULL, &si, &pi);
        if (!succeeded) {
            Log("cannot launch process for user with error 0x%x.", GetLastError());
        } else {
            nres = 0;
        }
    
        DestroyEnvironmentBlock(env);
        CloseHandle(dup);
    
        return nres;
    }
    

What do I miss here?


Solution

  • It's not the LNK file that is missing, but its target.

    Seems like a WOW64 issue -- for your 32-bit service, %WINDIR%\System32 actually redirects to SysWOW64 and these executable files do not exist there.

    Well, actually your 32-bit service is finding the 32-bit cmd.exe which does exist in SysWOW64, and then 32-bit cmd.exe has the above problem, when looking up the path %windir%\system32\SnippingTool.exe found in the shortcut .

    I can reproduce the problem using a 32-bit Command Prompt. 32-bit processes attempting to use these shortcuts simply fail.

    Try spawning the native version of cmd.exe (64-bit on your system), using %WINDIR%\SysNative\cmd.exe

    In addition, you have quoting problems. You're trying to nest quotes, but what actually happens is that the second quote matches the first quote and exits quoting, rather than nesting.

    In the future, when things fail in a service it is helpful to run the same call from a normal console application. In this case you would have immediately discovered that the issue is completely unrelated to impersonation. Second step, if it worked from a console application running in-profile would be using "Run As" with the console application, to test impersonation logic, still without the additional complexity of the service environment.