6

Service is "Allow Service to Interact with 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.
Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
Andrew
  • 61
  • 1
  • 2

3 Answers3

15

In Vista and later, a service won't be able to take a screenshot, or otherwise interact with the desktop -- "Allow Service to Interact with Desktop" is no longer supported. Services run in an isolated session that can't interact with the desktop. For more details, read up on "session 0 isolation".

For more background about why, this thread explains:

As multiples sessions are running because of terminal services or remote desktop connections there is no one to one relationship between a service and an interactive window station with one desktop. You can have one per interactive session. Which one should the service talk to? What if nobody looks at any desktop of the machine your service runs on - nobody notices that messagebox or whatever UI stuff.

Relying on that "feature" is just not adequate anymore. Get rid of it, there will be no alternative.

Community
  • 1
  • 1
Joe White
  • 94,807
  • 60
  • 220
  • 330
5

As an addition to Joe White's answer:

Most apps that have both a service and a UI nowadays are split in multiple processes: at least one service, and at least one (autostarting) UI process.

These processes communicate with each others through IPC and Synchronization objects like (Named) Pipes, (Memory Mapped) files, Mail slots, Queues, Events, Mutexes, Semaphores, etc. Note there is some overlap in those objects (some regarded more as IPC, others more like synchronization). A good start for Windows is at the MSDN Inteprocess Communications page.

This is for instance how Input Director works. It consists of these processes:

  1. C:\Program Files (x86)\Input Director\IDWinService.exe
  2. C:\Program Files (x86)\Input Director\InputDirectorSessionHelper.exe
  3. C:\Program Files (x86)\Input Director\InputDirector.exe
  4. C:\Program Files (x86)\Input Director\InputDirectorClipboardHelper.exe

Number 1. runs as a service process and loads 2.
Number 3. runs as a UI process and loads 4.

A great way of observing how these interact is through Process Explorer and Process Monitor from SysInternals.

Jeroen Wiert Pluimers
  • 23,965
  • 9
  • 74
  • 154
  • 3
    Why was this downvoted? (As services are not my area of expertise, I am curious.) – Andreas Rejbrand Jul 21 '11 at 19:49
  • @Jeroen What message are you trying to get across here? You have a list of executable names and a commendation for the SysInternals suite. I can't see the message and I want to downvote. – David Heffernan Jul 21 '11 at 20:18
  • Also, events, mutexes and sempaphores aren't really IPC mechanisms. They are synchronisation objects. Anonymous pipes are a good example of IPC between service and desktop app. Rather than asking why this was downvoted, I'd ask why it was upvoted 3 times. – David Heffernan Jul 21 '11 at 20:40
  • @David I was trying to explain how you can split the monolithic service that contains both logic and UI into multiple processes. That was: until dinner was ready :) – Jeroen Wiert Pluimers Jul 22 '11 at 09:49
  • 4
    @David, I don't see why you dislike Jeroen's answer; I upvoted it as helpful. My answer was focused on "why doesn't it work?"; Jeroen answered the obvious follow-up question, "how do I make it work?", viz., split your code into a service and a separate UI process. I don't see what's unclear or unhelpful about this answer -- if anything, it's probably more helpful toward OP's ultimate goal than mine was, and that sort of answer routinely gets upvoted on SO. – Joe White Jul 22 '11 at 12:07
0

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

1- The Easy Way, create a separate DLL and put the screen capturing code in it and then use CreateRemoteThread to load the DLL in the user's process (DLL Injection) let's say (explorer.exe). Here's and example of 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 a non-unicode Delphi version (<2009), for the unicode you should multiply the Length of 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 injects the DLL and starts capturing, I suggest that you put in the DLL the code that checks for your service status to behave accordingly, you could use multiple threads but be careful you should not create threads in the DLLMain since it may cause a deadlock.

2- The hard way, you could inject you whole code without creating a separate DLL, you could check this article which covers it all, it's in C++, but it's very useful and not that hard to understand:

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

helloworld0101
  • 140
  • 1
  • 7