2
votes

I'm trying to make a tiny tool for our system that would prevent windows shutdowns while our software runs. In order to make it independent, I made a separate application that prevent shutdown using this information.

However, when the application is minimized to tray, Windows simply kills it and shuts down normally. If the form is visible (aka I comment the Application.Minimize call from Form.OnCreate event), it does prevent the shutdown fine.

How could I achieve the MainWindow hook to keep live, or maybe get some other way of preventing system shutdowns, with the app being hidden in the tray?

Thanks.

Current code:

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, TlHelp32, dateutils, Vcl.AppEvnts, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    TrayIcon1: TTrayIcon;
    ApplicationEvents1: TApplicationEvents;
    procedure FormCreate(Sender: TObject);
    function HookEndSession(var Message: TMessage): Boolean;
    procedure WMQueryEndSession(var Msg : TWMQueryEndSession) ;
         message WM_QueryEndSession;
    procedure ApplicationEvents1Minimize(Sender: TObject);
    procedure ApplicationEvents1Restore(Sender: TObject);
    procedure TrayIcon1DblClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var Form1: TForm1;
var Mutex : THandle;

implementation

{$R *.dfm}



procedure TForm1.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
  Msg.Result := 0;
end;

function TForm1.HookEndSession(var Message: TMessage): Boolean;
begin
  result := false;
  if Message.Msg = WM_ENDSESSION then begin
    Message.Result := 0;
    result := true;
  end;
end;


procedure TForm1.TrayIcon1DblClick(Sender: TObject);
begin
  WindowState := wsNormal;
  Application.Terminate;
end;

procedure TForm1.ApplicationEvents1Minimize(Sender: TObject);
begin
  Hide();
  WindowState := wsMinimized;
  TrayIcon1.Visible := True;
end;

procedure TForm1.ApplicationEvents1Restore(Sender: TObject);
begin
  Application.Minimize;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Mutex := CreateMutex(nil, True, 'preventWinShutdown');
  if (Mutex = 0) OR (GetLastError = ERROR_ALREADY_EXISTS) then
  Application.Terminate;

  Application.HookMainWindow(HookEndSession);

  TrayIcon1.Hint := 'Windows Shutdown prevented.';
  //Application.Minimize;
end;


end.
2

2 Answers

4
votes

I don't have XE3 at hand but in XE7, TCustomTrayIcon.WindowProc explicitly handles WM_QUERYENDSESSION by returning 1 (equivalent to TRUE) which signals to Windows to go ahead with the logoff/shutdown/restart sequence. You can override it:

type
  TTrayIcon = class(Vcl.ExtCtrls.TTrayIcon)
  protected
    procedure WindowProc(var Message: TMessage); override;
  end;

procedure TTrayIcon.WindowProc(var Message: TMessage);
begin
  case Message.Msg of
    WM_QUERYENDSESSION:
      Message.Result := 0;
    else
      inherited WindowProc(Message);
  end;
end;

For a more complete solution, you would create a new class in a separate unit, inheriting from TCustomTrayIcon and re-publishing properties and events you wish to expose, like TTrayIcon does, install the component in the IDE and use it in any of your projects.

The above example is just a quick one with an interposer class which you can simply declare before your TForm1 and use immediately, in this unit only.

4
votes

What Ondrej said is only half of the solution.

On Vista and later, you must use ShutdownBlockReasonCreate(). This is especially important in your case because of the following caveats:

Application Shutdown Changes in Windows Vista

Applications that must block shutdown should use the new shutdown reason API

In Windows XP, Microsoft recommended that if an application needed to block shutdown, it should display its own UI explaining why it needed to do so, so users would be less frustrated when shutdown failed. As discussed earlier, Windows Vista will reduce user frustration when shutdown fails even more, by displaying new UI that lists all the reasons applications have provided for blocking shutdown.

In Windows Vista, if your application must block shutdown, in addition to returning FALSE or not responding to WM_QUERYENDSESSION, it should leverage this new UI by using a simple API to provide Windows with a reason string explaining why it is blocking shutdown. This API is straightforward:

BOOL ShutdownBlockReasonCreate(HWND hWnd, LPCWSTR pwszReason);

BOOL ShutdownBlockReasonDestroy(HWND hWnd);

BOOL ShutdownBlockReasonQuery(HWND hWnd, LPWSTR pwszBuff, DWORD *pcchBuff);

Use of this API is detailed more fully later in this topic, as well as in the MSDN documentation for the individual ShutdownBlockReason() functions.

Again, note that this API does not replace the need to return FALSE (or delay responding) to WM_QUERYENDSESSION to block shutdown. Applications need to do this in addition to using the API. Applications that return TRUE to WM_QUERYENDSESSION will be closed at shutdown, regardless of whether they have used the API.

Also note that if your application has no visible top-level windows, it must use this API if it needs to successfully block shutdown. Such applications will automatically be terminated if they block shutdown without using the API.