'As Administrator' folder - Lister shows images only as text

The behaviour described in the bug report is either by design, or would be far too complex/time-consuming to be changed

Moderators: white, Hacker, petermad, Stefan2

Post Reply
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

'As Administrator' folder - Lister shows images only as text

Post by *MarcinW »

Lister is unable to display multimedia content of the image/animation file, that is located in a directory that requires administrator rights.

Steps to reproduce:
1) create directory c:\test,
2) copy any image or animation file to c:\test, that TC is able to display with F3 - some BMP, PNG, AVI, etc.
3) select image/animation file and press F3 - multimedia content will be shown,
4) now go to c:\, open properties of c:\test and, in the Security tab:
- Windows 7: remove access rights for all groups - leave only Administrators group,
- Windows XP: remove all access rights, and then add access rights only for some user, that you are currently not logged in as,
5) go into c:\test again - select "As Administrator" option when prompted,
6) select image/animation file and press F3 - image or animation will NOT be shown, text contents will be shown instead; selecting Options -> Image/Multimedia will not help.

Launching TC in administrator mode solves the problem, but even in the normal mode (after using "As Administrator" option) the image/animation file contents are accessible for Lister (Lister displays contents as text), so Image/Multimedia mode should also work properly.

Additional note: also comparing (Files -> Compare By Contents) contents of the file located in c:\test with some other file fails - "Access denied on file" message is shown.

Regards
User avatar
Horst.Epp
Power Member
Power Member
Posts: 6493
Joined: 2003-02-06, 17:36 UTC
Location: Germany

Post by *Horst.Epp »

Confirmed under Windows 8.1 Update 1 x64
with TC 8.51a x64
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48083
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

Sorry, this is unfortunately impossible. Neither TC plugins nor the internal media player control can access files which require admin rights for reading. The reading has to be done via tcmadmin.exe tool, but the functions require a file handle or even a file name.
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

I don't know exactly how it works, so I have two questions:
- what is the difference between Lister and media player in Lister - isn't this the same process with same file access rights?
- what about "Compare By Contents" tool?

However, I can see some potential general solutions:
- opening a file in tcmadmin.exe and passing the file handle to media player/CBC tool,
- as above, but with a file mapping handle instead of the file handle (could be useful for CreateDIBSection function - it can get a file mapping handle as a parameter).

Regards
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48083
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

what is the difference between Lister and media player in Lister - isn't this the same process with same file access rights?
Lister can request data from tcmadmin.exe through a Windows Pipe - MediaPlayer requires physical access to the media file itself.
what about "Compare By Contents" tool?
Currently there is no admin support in the compare tool.
opening a file in tcmadmin.exe and passing the file handle to media player/CBC tool,
This will not work (Windows access restrictions)
as above, but with a file mapping handle instead of the file handle (could be useful for CreateDIBSection function - it can get a file mapping handle as a parameter).
File mappings are process-specific, the file is only mapped into a specific process (tcmadmin.exe or totalcmd.exe, not both).
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

Thanks for explanations.

So, as I suppose, calling DuplicateHandle API and passing a duplicated handle to the less privileged process doesn't help?
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48083
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

Honestly I don't know whether DuplicateHandle works with UAC or not, there is no indication in the MSDN article:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms724251%28v=vs.85%29.aspx
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

DuplicateHandle works - below is some test code. This was tested with Windows Vista and should also work with more recent Windows versions.

For Windows NT4/2000/XP there is no UAC and "requireAdministrator" in manifest is ignored - so CreateProcessAsUser (or similar) should be used instead of ShellExecuteEx.

The best approach seems to make a call to ShellExecuteEx first, and - when it fails - to CreateProcessAsUser (after asking for user name and password).


Usage: Test.exe SomeFile
where SomeFile is a file accessible only in Administrator mode (see steps to reproduce in the first post)

Test.dpr:

Code: Select all

program Test;
uses
  Windows, SysUtils, ShellAPI;

{$IFDEF VER90} {Delphi 2}
{WARNING: ROUGH IMPLEMENTATION - FOR TESTING PURPOSES ONLY}
function AnsiQuotedStr(const S : string; Quote : Char) : string;
begin
  Result:=Quote+S+Quote;
end;
{$ENDIF}

const
  TESTADMINEXE = 'testadmin.exe';
var
  Caption : string;
  FileName : string;
  FileHandle : THandle;
  ShellExecuteInfo : TShellExecuteInfo;
  ShellExecuteFile : string;
  ShellExecuteParameters : string;
  RemoteExitCode : {$IFDEF VER90}Integer{$ELSE}Cardinal{$ENDIF};
  Buffer : packed array[0..100-1] of Byte;
  BytesRead : {$IFDEF VER90}Integer{$ELSE}Cardinal{$ENDIF};
begin
  Caption:=ExtractFileName(ParamStr(0));

  FileName:=ParamStr(1);
  if FileName = '' then
  begin
    MessageBox(0,'Enter file name as a parameter!',PChar(Caption),MB_ICONSTOP);
    Exit;
  end;

  FileHandle:=CreateFile(PChar(FileName),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
  if FileHandle <> INVALID_HANDLE_VALUE then
  begin
    CloseHandle(FileHandle);
    MessageBox(0,'File could be accessed in a normal way, exiting.',PChar(Caption),MB_ICONSTOP);
    Exit;
  end;

  FillChar(ShellExecuteInfo,SizeOf(ShellExecuteInfo),0);
  ShellExecuteFile:=ExtractFilePath(ParamStr(0))+TESTADMINEXE;
  ShellExecuteParameters:=IntToStr(GetCurrentProcessId)+' '+AnsiQuotedStr(FileName,'"');
  with ShellExecuteInfo do
  begin
    cbSize:=SizeOf(ShellExecuteInfo);
    fMask:=SEE_MASK_NOCLOSEPROCESS;
    lpFile:=PChar(ShellExecuteFile);
    lpParameters:=PChar(ShellExecuteParameters);
    nShow:=SW_SHOW;
  end;
  if not ShellExecuteEx(@ShellExecuteInfo) then
  begin
    MessageBox(0,PChar('ShellExecuteEx call failed with error code '+IntToStr(GetLastError)+'.'),PChar(Caption),MB_ICONSTOP);
    Exit;
  end;
  try
    WaitForSingleObject(ShellExecuteInfo.hProcess,INFINITE);
    FileHandle:=INVALID_HANDLE_VALUE;
    if not GetExitCodeProcess(ShellExecuteInfo.hProcess,RemoteExitCode) then
    begin
      MessageBox(0,PChar('GetExitCodeProcess call failed with error code '+IntToStr(GetLastError)+'.'),PChar(Caption),MB_ICONSTOP);
      Exit;
    end;
    try
      FileHandle:=Integer(RemoteExitCode); {x64: typecasting to Integer forces sign-extending from 32-bit value to 64-bit value}

      if (FileHandle = INVALID_HANDLE_VALUE) or (FileHandle = 0) then
      begin
        MessageBox(0,'Could not open file with '+TESTADMINEXE+'.',PChar(Caption),MB_ICONSTOP);
        Exit;
      end;

      if not ReadFile(FileHandle,Buffer[0],SizeOf(Buffer),BytesRead,nil) then
      begin
        MessageBox(0,PChar('ReadFile call failed with error code '+IntToStr(GetLastError)+'.'),PChar(Caption),MB_ICONSTOP);
        Exit;
      end;
      MessageBox(0,PChar('Successfully read '+IntToStr(BytesRead)+' bytes:'#13#10+string(PAnsiChar(@Buffer[0]))),PChar(Caption),MB_OK);
    finally
      CloseHandle(FileHandle);
    end;
  finally
    CloseHandle(ShellExecuteInfo.hProcess);
  end;
end.
TestAdmin.dpr:

Code: Select all

program TestAdmin;
uses
  Windows, SysUtils;

{$IFDEF VER90} {Delphi 2}
const
  PROCESS_DUP_HANDLE = $00000040;
  DUPLICATE_SAME_ACCESS = $00000002;
{$ENDIF}

var
  Caption : string;
  Code : Integer;
  ParentPID : Cardinal;
  FileName : string;
  ParentProcessHandle : THandle;
  FileHandle : THandle;
  DuplicatedFileHandle : THandle;
begin
  ExitCode:=Integer(INVALID_HANDLE_VALUE);

  Caption:=ExtractFileName(ParamStr(0));

  Val(ParamStr(1),ParentPID,Code);
  if Code <> 0 then
  begin
    MessageBox(0,'Enter parent process ID as a parameter!',PChar(Caption),MB_ICONSTOP);
    Exit;
  end;

  FileName:=ParamStr(2);
  if FileName = '' then
  begin
    MessageBox(0,'Enter file name as a parameter!',PChar(Caption),MB_ICONSTOP);
    Exit;
  end;

  ParentProcessHandle:=OpenProcess(PROCESS_DUP_HANDLE,False,ParentPID);
  if ParentProcessHandle = 0 then
  begin
    MessageBox(0,PChar('OpenProcess call failed with error code '+IntToStr(GetLastError)+'.'),PChar(Caption),MB_ICONSTOP);
    Exit;
  end;
  try
    FileHandle:=CreateFile(PChar(FileName),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
    if FileHandle = INVALID_HANDLE_VALUE then
    begin
      MessageBox(0,PChar('CreateFile call failed with error code '+IntToStr(GetLastError)+'.'),PChar(Caption),MB_ICONSTOP);
      Exit;
    end;
    try
      if not DuplicateHandle(GetCurrentProcess,FileHandle,ParentProcessHandle,@DuplicatedFileHandle,0,True,DUPLICATE_SAME_ACCESS) then
      begin
        MessageBox(0,PChar('DuplicateHandle call failed with error code '+IntToStr(GetLastError)+'.'),PChar(Caption),MB_ICONSTOP);
        Exit;
      end;

      {ExitCode is always 32-bit, file handle may be 64-bit, but:
       "64-bit versions of Windows use 32-bit handles for interoperability.
       When sharing a handle between 32-bit and 64-bit applications, only
       the lower 32 bits are significant, so it is safe to truncate the handle
       (when passing it from 64-bit to 32-bit) or sign-extend the handle
       (when passing it from 32-bit to 64-bit)."}
      ExitCode:=DuplicatedFileHandle;
    finally
      CloseHandle(FileHandle);
    end;
  finally
    CloseHandle(ParentProcessHandle);
  end;

{$IFDEF VER90} {Delphi 2}
  {Delphi 2 overwrites ExitCode value with 0 without this call}
  Halt(ExitCode);
{$ENDIF}
end.
TestAdmin.exe.manifest (place in the same place as TestAdmin.exe):

Code: Select all

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity
    type="win32"
    name="DelphiApplication"
    version="1.0.0.0"
    processorArchitecture="*"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel
          level="requireAdministrator"/>
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48083
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

I will test it, thanks!
Author of Total Commander
https://www.ghisler.com
Post Reply