Why does the screenshot not work (black screen)?

Service "Allow service to interact with the desktop."

unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs; type TCopyDesk = class(TService) procedure ServiceContinue(Sender: TService; var Continued: Boolean); procedure ServiceExecute(Sender: TService); procedure ServicePause(Sender: TService; var Paused: Boolean); procedure ServiceShutdown(Sender: TService); procedure ServiceStart(Sender: TService; var Started: Boolean); procedure ServiceStop(Sender: TService; var Stopped: Boolean); private procedure CopyScreen(const Index: Integer); public function GetServiceController: TServiceController; override; end; var CopyDesk: TCopyDesk; implementation {$R *.DFM} procedure ServiceController(CtrlCode: DWord); stdcall; begin CopyDesk.Controller(CtrlCode); end; procedure TCopyDesk.CopyScreen(const Index: Integer); const DefaultWindowStation = 'WinSta0'; DefaultDesktop = 'Default'; CAPTUREBLT = $40000000; WINSTA_ALL_ACCESS = $0000037f; var Bmp: TBitmap; hwinstaSave: HWINSTA; hdeskSave: HDESK; hwinstaUser: HWINSTA; hdeskUser: HDESK; dwThreadId: DWORD; hdcScreen : HDC; hdcCompatible : HDC; hbmScreen : HBITMAP; begin hwinstaUser:= OpenWindowStation(DefaultWindowStation, FALSE, WINSTA_ALL_ACCESS); hwinstaSave:= GetProcessWindowStation; if hwinstaUser = 0 then begin OutputDebugString(PChar('OpenWindowStation failed' + SysErrorMessage (GetLastError))); exit; end; if not SetProcessWindowStation(hwinstaUser) then begin OutputDebugString('SetProcessWindowStation failed'); exit; end; // hdeskUser:= OpenDesktop(DefaultDesktop, 0, FALSE, MAXIMUM_ALLOWED); hdeskUser:= OpenInputDesktop(0, False, MAXIMUM_ALLOWED); if hdeskUser = 0 then begin OutputDebugString('OpenDesktop failed'); SetProcessWindowStation (hwinstaSave); CloseWindowStation (hwinstaUser); exit; end; dwThreadId:= GetCurrentThreadID; hdeskSave:= GetThreadDesktop(dwThreadId); if not SetThreadDesktop(hdeskUser) then begin OutputDebugString(PChar('SetThreadDesktop' + SysErrorMessage(GetLastError))); Exit; end; try hdcScreen := GetDC(0);//GetDC(GetDesktopWindow);//CreateDC('DISPLAY', nil, nil, nil); hdcCompatible := CreateCompatibleDC(hdcScreen); hbmScreen := CreateCompatibleBitmap(hdcScreen, GetDeviceCaps(hdcScreen, HORZRES), GetDeviceCaps(hdcScreen, VERTRES)); SelectObject(hdcCompatible, hbmScreen); bmp:= TBitmap.Create; bmp.Handle:= hbmScreen; BitBlt(hdcCompatible, 0,0, bmp.Width, bmp.Height, hdcScreen, 0,0, SRCCOPY OR CAPTUREBLT); Bmp.SaveToFile('C:\Users\Public\ScreenShot\' + IntToStr(Index) + '.bmp'); finally DeleteDC(hdcScreen); DeleteDC(hdcCompatible); Bmp.Free; Bmp:= nil; end; SetThreadDesktop(hdeskSave); SetProcessWindowStation(hwinstaSave); if hwinstaUser <> 0 then CloseWindowStation(hwinstaUser); if hdeskUser <> 0 then CloseDesktop(hdeskUser); end; function TCopyDesk.GetServiceController: TServiceController; begin Result := ServiceController; end; procedure TCopyDesk.ServiceContinue(Sender: TService; var Continued: Boolean); begin while not Terminated do begin Sleep(10); ServiceThread.ProcessRequests(False); end; end; procedure TCopyDesk.ServiceExecute(Sender: TService); var Index: Integer; begin Index:= 0; while not Terminated do begin CopyScreen(Index); Inc(Index); ServiceThread.ProcessRequests(False); // Sleep(1000); // if Index = 4 then DoStop; end; end; procedure TCopyDesk.ServicePause(Sender: TService; var Paused: Boolean); begin Paused:= True; end; procedure TCopyDesk.ServiceShutdown(Sender: TService); begin Status:= csStopped; ReportStatus(); end; procedure TCopyDesk.ServiceStart(Sender: TService; var Started: Boolean); begin Started:= True; end; procedure TCopyDesk.ServiceStop(Sender: TService; var Stopped: Boolean); begin Stopped:= True; end; end. 
+6
source share
3 answers

In Vista and later versions, the service will not be able to take a screenshot or otherwise interact with the desktop - “Allow the service to interact with the desktop” is no longer supported. Services are launched in an isolated session that cannot interact with the desktop. For more information, read Session Isolation 0 .

More on why, this thread explains :

Because sessions with multiple sessions work due to Terminal Services or Remote Desktop Connections, there is no connection between the service and the single-desktop interactive window station. You may have one for each interactive session. What services do you need to talk to? What to do if no one is looking at any desktop of the machine on which your service is running - no one notices this mailbox or something in it.

Relying on this “feature” is simply not enough. Get rid of this, there will be no alternative.

+15
source

As an addition to Joe White:

Most applications that have both a service and a user interface are currently divided into several processes: at least one service and at least one (autostart) user interface process.

These processes interact with each other through IPC and Synchronization objects, such as (named) pipes, memory files, mail slots, queues, events, mutexes, semaphores, etc. Please note that there is some overlap in these objects (some of them are treated as IPC, others as synchronization). A good start for Windows is on the MSDN Inteprocess Communications page.

This, for example, runs the "Input Director." It consists of these processes:

  • C: \ Program Files (x86) \ Input Director \ IDWinService.exe
  • C: \ Program Files (x86) \ Input Director \ InputDirectorSessionHelper.exe
  • C: \ Program Files (x86) \ Input Director \ InputDirector.exe
  • C: \ Program Files (x86) \ Input Director \ InputDirectorClipboardHelper.exe

Number 1. Works as a service process and loads 2.
Number 3. runs as a user interface process and loads 4.

A great way to see how these interactions interact through Process Explorer and Process Monitor from SysInternals .

+5
source

Joe White is right, but there is a workaround to this problem, you can create a remote thread in a process created in a user session (session 1 or so) using the CreateRemoteThread function, and there are two ways for you: for this:

1- An easy way is to create a separate DLL and place the screen capture code into it, and then use CreateRemoteThread to load the DLL into the user process (DLL Injection), let it say (explorer.exe). Here is an example DLL Injection:

  var PID: Cardinal; DLL_Name: string; pDLL: Pointer; hProcess, BW: Cardinal ; hRemote_Thread: Cardinal; begin DLL_Name := 'C:\ScreenCap.dll'; PID := 3052; // (explorer.exe process ID) hProcess := OpenProcess(PROCESS_ALL_ACCESS, false, PID); pDLL := VirtualAllocEx(hProcess, 0, Length(DLL_Name), MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory(hProcess, pDLL, PChar(DLL_Name), Length(DLL_Name), BW); CreateRemoteThread(hProcess, nil, 0, GetProcAddress(GetModuleHandle('kernel32.dll'), 'LoadLibraryA'), pDLL, 0, hRemote_Thread); CloseHandle(hProzess); end; 

This is for the non-Unicode version of Delphi (<2009), for unicode you should multiply the length of the DLL_Name by the size of Char (Length (DLL_Name) * SizeOf (Char)) in both VirtualAllocEx and WriteProcessMemory, and you can use LoadLibraryW instead.

When your service starts, it enters the DLL and starts writing, I suggest that you add code to the DLL that checks your service status, behave accordingly, you can use several threads, but be careful, you should not create threads in DLLMain, since it can cause a dead end.

2- The difficult way, you can enter all the code without creating a separate DLL, you can check this article, which covers all this, in C ++, but it is very useful and not so difficult to understand:

http://www.codeproject.com/KB/threads/winspy.aspx

0
source

Source: https://habr.com/ru/post/893301/


All Articles