Recent

Author Topic: Get current user desktop path - when application is run as administrator  (Read 1259 times)

LazProgger

  • Jr. Member
  • **
  • Posts: 91
I am using the following function to get the path to the desktop of the user running the application:

Code: Pascal  [Select]
  1. uses windows, shlobj, ActiveX, ComObj;
  2.  
  3. function _GetSpecialFolder(const CSIDL: integer) : string;
  4. var
  5.     RecPath : PWideChar;
  6. begin
  7.     result := '';
  8.     RecPath := WideStrAlloc(MAX_PATH);
  9.     try
  10.       FillChar(RecPath^, MAX_PATH, 0);
  11.       if SHGetFolderPathW(0, CSIDL, 0, 0, RecPath) = 0 then result := RecPath;
  12.     finally
  13.       StrDispose(RecPath);
  14.     end;
  15. end;

You can call it like that:

Code: Pascal  [Select]
  1. APathToDesktop := _GetSpecialFolder(CSIDL_DESKTOPDIRECTORY);

That's working fine. When the application is run as normal user. However, it's not working when run as administrator. Then the result is the desktop of the administrator, but I need the desktop of the current user running the current windows session.

I have found out that you can use the third parameter of SHGetFolderPathW to path a user token id to get the right path.

But how to find out that user id? I have tried approaches like:

Code: Pascal  [Select]
  1. OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, TokenResult)

But again, TokenResult is always the administrator token (the current thread is run as administrator, that's right). How to get the token of the user that have started Windows and only clicked on "Run as Administrator"?

ASBzone

  • Sr. Member
  • ****
  • Posts: 253
  • Automation leads to relaxation...
    • BrainWaveCC Utilities
That's working fine. When the application is run as normal user. However, it's not working when run as administrator. Then the result is the desktop of the administrator, but I need the desktop of the current user running the current windows session.

On Windows desktop editions, you could look for the other logged in user, but on server that would not necessarily be correct, as multiple users could be logged on.

I'm not even sure what question to ask for this search...  :-[

But you can look at what users own the Explorer process.      (A search confirmed that someone else thinks this is a good idea!)

https://stackoverflow.com/questions/5218778/how-do-i-get-the-currently-logged-username-from-a-windows-service-in-net
-ASB: https://www.BrainWaveCC.com

Lazarus v2.0.5 r62023 / FPC v3.2.0-beta-r43192 (via FpcUpDeluxe) -- Windows 64-bit install w/32-bit cross-compile
Primary System: Windows 10 Pro x64, Version 1903 (Build 18362.418)
Other Systems: Windows 10 Pro x64, Version 1903 or greater

LazProgger

  • Jr. Member
  • **
  • Posts: 91
I have tried to implement the explorer process idea, you have mentioned. In this case I have used the Shell_TrayWnd process which should be run by the user that has started the system:

Code: Pascal  [Select]
  1. procedure TForm1.Button8Click(Sender: TObject);
  2. var
  3.   ATrayHandle: HWND;
  4.   ATrayThreadProcessID: DWORD;
  5.   ATrayProcessHandle: THandle;
  6.   AUserToken: THandle;
  7.  
  8.   function _GetSpecialFolder(const CSIDL: integer; AUser: THANDLE) : string;
  9.   var
  10.       RecPath : PWideChar;
  11.   begin
  12.       result := '';
  13.       RecPath := WideStrAlloc(MAX_PATH);
  14.       try
  15.         FillChar(RecPath^, MAX_PATH, 0);
  16.         if SHGetFolderPathW(0, CSIDL, AUser, 0, RecPath) = 0 then result := RecPath;
  17.       finally
  18.         StrDispose(RecPath);
  19.       end;
  20.   end;
  21.  
  22. begin
  23.  
  24.   AUserToken := 0;
  25.  
  26.   ATrayHandle := FindWindow('Shell_TrayWnd', nil);
  27.   if ATrayHandle <> 0 then begin
  28.      if GetWindowThreadProcessId(ATrayHandle, @ATrayThreadProcessID) <> 0 then begin
  29.         ATrayProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION, False, ATrayThreadProcessID);
  30.         if not OpenProcessToken(ATrayProcessHandle, TOKEN_QUERY, AUserToken) then begin
  31.           if GetLastError = ERROR_NO_TOKEN then begin
  32.              OpenProcessToken(ATrayHandle, TOKEN_QUERY, AUserToken);
  33.           end;
  34.         end;
  35.      end;
  36.   end;  
  37.  
  38.   ShowMessage(_GetSpecialFolder(CSIDL_DESKTOPDIRECTORY, AUserToken));
  39.  
  40. end;

In my first tests, this code is working for both situations: If the program is run or not run as administrator.

However, this solution seems to be not very clean for me. Okay, you might think there is always a Shell_TrayWnd process, but how reliable is this across different system versions and in future? What do you think? And do you have ideas to improve that code?

« Last Edit: March 18, 2019, 02:17:57 pm by LazProgger »

ASerge

  • Hero Member
  • *****
  • Posts: 1411
And do you have ideas to improve that code?
Use GetShellWindow. And what about checking the result and closing the handles?
Code: Pascal  [Select]
  1. uses Windows, ShlObj;
  2.  
  3. function GetShellWindow: HWND; stdcall; external user32;
  4.  
  5. procedure TForm1.Button1Click(Sender: TObject);
  6.  
  7.   function GetSpecialFolder(const CSIDL: LongInt; AUser: THandle): string;
  8.   var
  9.     Buffer: array[0..MAX_PATH] of WideChar;
  10.   begin
  11.     Result := '';
  12.     Buffer[0] := #0; // To skip compiler warning
  13.     if SHGetFolderPathW(0, CSIDL, AUser, SHGFP_TYPE_CURRENT, Buffer) = S_OK then
  14.       Result := Buffer;
  15.   end;
  16.  
  17. var
  18.   ShellWindow: HWND;
  19.   ShellProcessId: DWORD;
  20.   ShellProcessHandle: THandle;
  21.   ShellUserToken: THandle;
  22. begin
  23.   ShellUserToken := 0;
  24.   ShellWindow := GetShellWindow;
  25.   if ShellWindow <> 0 then
  26.   begin
  27.     GetWindowThreadProcessId(ShellWindow, @ShellProcessId);
  28.     ShellProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION, False, ShellProcessId);
  29.     if ShellProcessHandle <> 0 then
  30.     begin
  31.       if not OpenProcessToken(ShellProcessHandle, TOKEN_QUERY, @ShellUserToken) then
  32.         ShellUserToken := 0;
  33.       CloseHandle(ShellProcessHandle);
  34.     end;
  35.   end;
  36.   Caption := GetSpecialFolder(CSIDL_DESKTOPDIRECTORY, ShellUserToken);
  37.   if ShellUserToken <> 0 then
  38.     CloseHandle(ShellUserToken);
  39. end;

LazProgger

  • Jr. Member
  • **
  • Posts: 91
Thank you very much for the code improvement.

Oh, I did not know that you have to close the handles. Is that always necessary when working with handles?

And is there a reason for changing dwflags from 0 to SHGFP_TYPE_CURRENT? I have tested both versions with or without that flag and it is producing the same results.

And a last question: When GetShellWindow is defined in user32. Could there be problems when using that code in a 64 bit environment or a 64 bit application?

ASerge

  • Hero Member
  • *****
  • Posts: 1411
Oh, I did not know that you have to close the handles. Is that always necessary when working with handles?
Yes, to exclude resource leak.
Quote
And is there a reason for changing dwflags from 0 to SHGFP_TYPE_CURRENT? I have tested both versions with or without that flag and it is producing the same results.
It's equal. Just so more clearly expressed, you need the current, may be the redirected location, not the "default".
Quote
And a last question: When GetShellWindow is defined in user32. Could there be problems when using that code in a 64 bit environment or a 64 bit application?
According to the documentation, at least for Windows 2000. I tested this on 64-bit Windows.