0

I saw Stack Overflow question How to switch a process between default desktop and Winlogon desktop?.

And I have produced a minimal test-case creating a console project application, but SetThreadDesktop() does not switch my program to the target desktop.

Why does this happen?

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Winapi.Windows,
  System.SysUtils,
  Vcl.Graphics,

function RandomPassword(PLen: Integer): string;
var
  str: string;
begin
  Randomize;
  str    := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  Result := '';
  repeat
    Result := Result + str[Random(Length(str)) + 1];
  until (Length(Result) = PLen)
end;

procedure Print;
var
  DCDesk: HDC;
  bmp: TBitmap;
  hmod, hmod2 : HMODULE;
  BitBltAPI: function(DestDC: HDC; X, Y, Width, Height: Integer; SrcDC: HDC; XSrc, YSrc: Integer; Rop: DWORD): BOOL; stdcall;
  GetWindowDCAPI: function(hWnd: HWND): HDC; stdcall;
begin
  hmod := GetModuleHandle('Gdi32.dll');
  hmod2:= GetModuleHandle('User32.dll');

  if (hmod <> 0) and (hmod2 <> 0) then begin
    bmp := TBitmap.Create;
    bmp.Height := Screen.Height;
    bmp.Width := Screen.Width;

    GetWindowDCAPI := GetProcAddress(hmod2, 'GetWindowDC');
    if (@GetWindowDCAPI <> nil) then begin
      DCDesk := GetWindowDCAPI(GetDesktopWindow);
    end;

    BitBltAPI := GetProcAddress(hmod, 'BitBlt');
    if (@BitBltAPI <> nil) then begin
      BitBltAPI(bmp.Canvas.Handle, 0, 0, Screen.Width, Screen.Height, DCDesk, 0, 0, SRCCOPY);
      bmp.SaveToFile('ScreenShot_------_' + RandomPassword(8) + '.bmp');
    end;

    ReleaseDC(GetDesktopWindow, DCDesk);

    bmp.Free;

    FreeLibrary(hmod);
    FreeLibrary(hmod2);
  end;
end;

//===============================================================================================================================

var
  hWinsta, hdesktop:thandle;
begin
  try
    while True do
    begin
      hWinsta := OpenWindowStation('WinSta0', TRUE, GENERIC_ALL);

      If hwinsta <> INVALID_HANDLE_VALUE then
      begin
        SetProcessWindowStation (hWinsta);
        hdesktop := OpenDesktop ('default_set', 0, TRUE, GENERIC_ALL);
        if (hdesktop <> INVALID_HANDLE_VALUE) then
          if SetThreadDesktop (hdesktop) then
          begin
            Print; // Captures screen of target desktop.

            CloseWindowStation (hwinsta);
            CloseDesktop (hdesktop);
          end;
      end;
      Sleep(5000);
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

end.

Checking errors, the SetThreadDesktop() call fails with error code 170 (ERROR_BUSY, The requested resource is in use) when the target desktop is open.

var
  threahdesk: boolean;

  ...

  threahdesk := SetThreadDesktop (hdesktop);
  ShowMessage(IntToStr(GetLastError));

  if threahdesk Then
  begin
    Print;

    CloseWindowStation (hwinsta);
    CloseDesktop (hdesktop);
  end;

After that I saw several suggestion in some forums, my actual code is as follows:

var
hWinsta, hdesktop:thandle;
threahdesk, setprocwst: Boolean;

////////////////////////////////////////////////////////////////////////////////

begin
  try

    while True do

    begin

      Application.Free;

      hWinsta:= OpenWindowStation('WinSta0', TRUE, GENERIC_ALL);

      If hwinsta <> 0 Then
      Begin

        setprocwst := SetProcessWindowStation(hWinsta);

        if setprocwst then

          hdesktop:= OpenDesktop('default_set', 0, TRUE, GENERIC_ALL);

        If (hdesktop <> 0) Then

          threahdesk := SetThreadDesktop(hdesktop);

        Application := TApplication.Create(nil);
        Application.Initialize;
        Application.Run;

        If threahdesk Then
        Begin

          Print;

          CloseWindowStation (hwinsta);
          CloseDesktop (hdesktop);
        End;
      End;

      Sleep(5000);
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

end.
Community
  • 1
  • 1
  • In one of comments on link above, was said that he have created a console application for test-case and worked fine. So, i want know why not is working here on my code above? :-( –  Dec 07 '16 at 03:20
  • I edited my question. –  Dec 07 '16 at 03:48
  • 1
    It is not valid to call `GetLastError()` after `SetThreadDesktop()` exits unless it returned FALSE, which you are not checking for. Otherwise, you might be picking up an error code from an earlier API call. Also, `OpenDesktop()` returns 0 on failure, not `INVALID_HANDLE_VALUE` (-1). And you are not checking `SetProcessWindowStation()` for failure. And you are calling `CloseWindowStation()` and `CloseDesktop()` at the wrong places. And why are you loading `GetWindowDC()` and `BitBlt()` dynamically, but are using `ReleaseDC()` statically? Your code is full of issues. – Remy Lebeau Dec 07 '16 at 04:17
  • @RemyLebeau, my screenshot function works fine. No exist trouble on your execution. –  Dec 07 '16 at 04:26
  • `SetProcessWindowStation` is returning -1 ( Success ) that is different of 0 –  Dec 07 '16 at 04:36
  • @RemyLebeau, you know how my code could works? i have done all that you suggested on last comment, but `SetThreadDesktop` still returns false. Also want, If possible a suggestion of new code ( independent if will works or no. ). –  Dec 07 '16 at 08:36
  • I tried use [this suggestion](http://blog.delphi-jedi.net/2008/03/19/how-to-use-vcl-and-setthreaddesktop/) on my code above, but not had success. Could help me, please? seem that apparently it works. @Dmitriy Sorokin, also left a link here, i also tried, but nothing until now. I saw [this example](http://www.delphigroups.info/3/cd/34122.html) where suggestion also is the same. –  Dec 08 '16 at 14:12
  • Please make sure that no GUI APIs are called before you invoke SetThreadDesktop(). Try to minimize your codes. Create a console project and just write the codes related to SetThreadDesktop() in main function. – Leon Dec 09 '16 at 03:22
  • @Leon, could give me a C++ example of you teste-case where you said that works, please? or in Delphi. –  Dec 09 '16 at 03:27
  • 1
    @Saulo I cannot find my test-case, but I found my app written in C++ 3 years ago which can be run on secure desktop. I think it may be helpful to you, so I push the code to the GitHub. You can visit it by this link: [https://github.com/DeviLeo/TrackPadSimulator](https://github.com/DeviLeo/TrackPadSimulator). I cannot remember the most detail but I am certain that I start a service to start a daemon process to process keyboard and mouse operations on secure desktop. – Leon Dec 10 '16 at 11:32
  • @Leon, thank you, yes will be usefull to me. –  Dec 10 '16 at 12:26
  • What does ending the program in "`.`" mean (`end.`)? – Peter Mortensen Dec 11 '16 at 10:23

2 Answers2

1

From the SetThreadDesktop() documentation:

The SetThreadDesktop function will fail if the calling thread has any windows or hooks on its current desktop (unless the hDesktop parameter is a handle to the current desktop).

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Dimsa
  • 272
  • 4
  • 13
  • then this can be generating error _170 ERROR_BUSY_? If yes, then how make this function work fine as she promises? And In my case what window that is blocking `SetThreadDesktop`? some idea? –  Dec 07 '16 at 03:54
  • On the russian forum was the same problem only on the one computer. But there was no answer. You can try disable antivirus or try to remove all hooks with the UnhookWindowsHookEx by the handle bust. Here this topic: http://delphimaster.net/view/15-1235998097 – Dimsa Dec 07 '16 at 04:07
  • @ Dmitriy, i could accept your answer, is very good, but don't is a solution. –  Dec 07 '16 at 08:38
  • Someone here already had success with use this api ( `SetThreadDesktop` ).? –  Dec 07 '16 at 20:38
1

The answer by Dmitriy is accurate in that the function fails because the calling thread has windows or hooks, although it doesn't explain how so.

The reason SetThreadDesktop is failing with ERROR_BUSY is, you have "forms.pas" in your uses list. Although it's missing in the code you posted (semicolon in "uses" clause is also missing hinting more units), the use of the Screen global variable makes it evident that you have "forms" in uses. "Forms" pulls in "controls.pas" which initializes the Application object. In its constructor, the Application creates a utility window for its PopupControlWnd. There may be other windows created but this one is enough reason for the function to fail.

You use Screen for its width/height. Un-use "forms", you can use API to retrieve that information.

There are other issues in the code like missing/wrong error checking which have been mentioned in the comments to the question, but they are not relevant to why SetThreadDesktop fails.


Below sample program demonstrates there's no problem calling SetThreadDesktop in the main thread of a console application, provided there's a desktop with name 'default_set' in the window station in which the program is running and has access rights to.

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
//  Vcl.Forms,  // uncomment to get an ERROR_BUSY
  Winapi.Windows;

var
  hSaveDesktop, hDesktop: HDESK;
begin
  hSaveDesktop := GetThreadDesktop(GetCurrentThreadId);
  Win32Check(hSaveDesktop <> 0);

  hDesktop := OpenDesktop('default_set', 0, True, GENERIC_ALL);
  Win32Check(hDesktop <> 0);
  try
    Win32Check(SetThreadDesktop(hDesktop));
    try

      // --

    finally
      Win32Check(SetThreadDesktop(hSaveDesktop));
    end;
  finally
    Win32Check(CloseDesktop(hDesktop));
  end;
end.
Community
  • 1
  • 1
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • Thank you by your answer @Sertac, you can edit your answer with a better code please? All code above is compilable :-) –  Dec 09 '16 at 23:49
  • and about screen capture function and others functions that you said where can cause fails in `SetThreadDesktop`? –  Dec 10 '16 at 00:47
  • _Team View_ software for example, have some ideia about how them made to capture screen without use "`Bitmap`" class or "`Jpeg`" class? Because i think that Team View uses much `SetThreadDesktop` when user opens some software that run inside a new desktop. –  Dec 10 '16 at 01:36
  • @Saulo - I didn't say anything like it. I explained why the function failed as per the question. – Sertac Akyuz Dec 10 '16 at 01:58
  • Ok @Sertac, thank you, i already found a function to capture screen that works with this example :D. So if I happen to want to create a VCL Form project and use `SetThreadDesktop`, this do not will works, correct? –  Dec 10 '16 at 02:09
  • @Saulo - Correct. I don't know how it turns out though if you "free" the application. – Sertac Akyuz Dec 10 '16 at 02:11
  • Ok, i will accept you answer. Had asked about `SetThreadDesktop` api here in S.O, because i wants insert this functionality in my Remote Administration Tool, and also had saw on _Team View_ (based on your operation). My unique trouble now after that i saw working here based in your code above, is that my "client.exe" is in VCL Form ( socket communications, screen capture, etc ), but i will see what can do, for reuse of "client.exe" of some other way. Probably this can be more one question here soon :D –  Dec 10 '16 at 02:23
  • I have a another question: If my application is in Console mode, so if i put in `uses` session on source code of "program Project1" to a Unit that have a VCL Form ( eg: UFormContact), `SetThreadDesktop` also will works? or fails only if have `Vcl.Forms` here on scope ( `uses` session ) of "program Project1"? –  Dec 11 '16 at 18:56
  • @Saulo - It will fail. Once "controls.pas" is pulled in, even if you don't use anything from "forms" or "controls" it won't work. Because the initialization section of "controls" creates a window. – Sertac Akyuz Dec 11 '16 at 19:02
  • So, making all my VCL Form in dll format and call some function on "dll form" dynamically can be a possible solution to solve this? –  Dec 11 '16 at 19:11
  • @Saulo - You might like to read this [page](http://stackoverflow.com/questions/18932060/running-vcl-in-a-separate-thread). – Sertac Akyuz Dec 12 '16 at 22:58
  • Very interesting @Sertac, thank you very much. I already had said to you ( in comments above ) a idea similar to this answer gived by David Heffernan on this page that you suggested above. Make each Form of my software as a dll project. Now this about initialize "dll form" in a separate thread using `LoadLibray` api, is new for me :D. But you think that all this idea present on answer that was accepted for the question that you suggested will work in my case here? –  Dec 13 '16 at 00:55
  • I really don't know. – Sertac Akyuz Dec 13 '16 at 15:35
  • i will test, and return results here in case of success or fails. –  Dec 14 '16 at 00:21