Jump to content
Ian Branch

Detect Windows shutdown?

Recommended Posts

Hi Team,

D10.3.3.

I want to detect Windows shutdown in Win 7 and Win 10 in order to ensure data is saved before the App gets clobbered.

I have tried a few examples using..

  { Private declarations }
  procedure WMQueryEndSession(var Msg: TWMQueryEndSession);
  message WM_QUERYENDSESSION;

plus its associated routine, without success.

I suspect the above may be OK in Win 7, haven't tried it yet, but not for Win 10.

Anybody got code working as desired in Win 10?

Regards & TIA,

Ian

Share this post


Link to post

You should also listen for WM_POWERBROADCAST, but this has to be set-up to work, WM_ENDSESSION and WM_QUIT, lots of ways of closing down.

 

Angus

Share this post


Link to post

Not sure what you need WM_POWERBROADCAST for, but just working with WM_ENDSESSION and WM_QUERYENDSESSION has worked well for several kiosk applications, for several years now.

Share this post


Link to post

You also have WM_WTSSESSION_CHANGE that give some messages that allows you to take precautionary actions.
https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsregistersessionnotification

https://docs.microsoft.com/en-gb/windows/desktop/TermServ/wm-wtssession-change


procedure ON_WM_WTSSESSION_CHANGE(var Message: TMessage); message WM_WTSSESSION_CHANGE;

 

Note that WTS_SESSION_TERMINATE does not actually work, though.
 

Share this post


Link to post

Power broadcasts give warnings when the system is about to close down, while designed for battery powered kit, they do work on desktops, my PCs have USB connection to the APC UPS so can close down early.  Can not say specifically under what circumstances these messages appear, but belt and braces when saving data...

 

        PBT_APMBATTERYLOW: mess := 'Battery power is low' ;
        PBT_APMQUERYSUSPEND: mess := 'Request for permission to suspend' ;
        PBT_APMSUSPEND: mess := 'System is suspending operation' ;
 

Angus

Share this post


Link to post

Hi Guys,

Thanks to all for your input.

I am out of my league here but this is what I have att from Dr Google. ;-).

  private
  { Private declarations }
  DataToBeSaved: Boolean;
  procedure WMQueryEndSession(var Msg: TWMQueryEndSession);
    message WM_QueryEndSession; // detect Windows shutdown message
  procedure SaveData; // routine to save data to disk

Then..

procedure TMainForm.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
  if DataToBeSaved then
    if MessageDlg('Windows is shutting down! First save data changes?',
         mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin
      Msg.Result := 0; // disallow Windows from shutting down
      beep;
      SaveData;
    end;
  Msg.Result := 1;     // allow Windows shutdown
end;

// Set DataToBeSaved to False after saving data
procedure TMainForm.SaveData;
begin
  // Save data to disk files
  // ...
  DataToBeSaved := False;
end;
.....
.....
procedure TMainForm.FormCreate(Sender: TObject);
begin
  //
  DataToBeSaved := True;
  // 
  ....
  .... 

In my Win 10 dev PC, when I tell windows to shutdown it goes straight  to the green Shutting Down screen.  I never see the MessageDlg prompt. 😞

Am I missing some other nuance?

I noted David's comments and reference.  I need this to work on my Win 10 PC as well as the Customer's Win 7, later I hope to be Win 10, PCs.

Again, thoughts, suggestions, code appreciated.

 

Regards & TIA,

Ian

Share this post


Link to post
Posted (edited)
4 hours ago, Ian Branch said:

I never see the MessageDlg prompt.

This is documented here (read Remarks): https://docs.microsoft.com/en-us/windows/win32/shutdown/wm-queryendsession

If you really need to stop the shutdown: "Each application should return TRUE or FALSE immediately upon receiving this message"

So you must return immediately, not showing a message and wait...

But... "Applications should respect the user's intentions and return TRUE"

If possible it's better to save data in other place and next time you app is running again ask the user if he want to keep this data. Or even better, if you can, restore the app state exactly how it was at shutdown and you don't need to ask anything. The user will see your app exactly how it was at shutdown. That's how mobile app are supposed to work but it's nice also for desktop.

Edited by Cristian Peța

Share this post


Link to post

Hi Cristian,

Thank you for your input.

I don't want to stop it, just delay/pause it while the App writes to the log and closes the database gracefully.   Hence the 

procedure TMainForm.SaveData;

Then Windows can shut down.

ATT the message display is purely to confirm that something is happening in the routine.

Share this post


Link to post
Posted (edited)
4 hours ago, Ian Branch said:

if MessageDlg('Windows is shutting down! First save data changes?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin

This is not just a message. You are waiting for the user to press You or No. And in this time you are not returning to the OS. Then the OS will simply kill you app.

Edited by Cristian Peța

Share this post


Link to post

Hi Cristian,

Yes I know.  My issue/point is that I am NOT getting the message.

Hence it would seem that the procedure isn't being called.

Share this post


Link to post
Posted (edited)
22 minutes ago, Ian Branch said:

My issue/point is that I am NOT getting the message.

I must repeat: this is documented and is working "as designed"

If you want an other behavior and don't want to do what documentation says the you need to find an other OS.

 

Documentation says: "If your application must block or postpone system shutdown, use the ShutdownBlockReasonCreate function."

Then when you are finished use ShutdownBlockReasonDestroy.

Edited by Cristian Peța

Share this post


Link to post

Hi Cristian,

Clearly I don't understand the documentation.

 

Hi David,

IIUC the code had the 'True' & 'False' around the wrong way??

I also think the 

Msg.Result := 0; // disallow Windows from shutting down

was in the wrong place and should have been before the ShowMessage, AND should have been..

 Msg.Result := 1;  // disallow Windows from shutting down

I'm trying here guys but this is all new to me.

Share this post


Link to post

OK.  Didn't see the "ShutdownBlockReasonCreate function" note.

Had a look.  It's cryptic to me and doesn't really give me any insight as to what is required. 😞

 

Guys,

Let's forget about it.

 

Thank you for your attempts to educate me.  I apologise for my lack of knowledge/skill.

 

Share this post


Link to post

You can't have read the documentation. Because you aren't calling ShutdownBlockReasonCreate. 

 

I know that you want to solve this quickly. But that expectation is unrealistic. It will take you time to wade through this, try it out, read and understand example code, etc.

 

For sure one of us could write you some examples but unless you understand it will you really be able to integrate it into your code? Just because you don't understand this now does not mean that you can't learn it. It just requires self belief and persistence. 

Share this post


Link to post

OK.  I'll have another try.

I have had a look at the article above and am trying to integrate the ShutdownBlockReason.... functions.

Where are the declared/defined?

Share this post


Link to post
7 minutes ago, Ian Branch said:

OK.  I'll have another try.

I have had a look at the article above and am trying to integrate the ShutdownBlockReason.... functions.

Where are the declared/defined?

Did you read, what Cristian Peța linked?

 

function ShutdownBlockReasonCreate(hWnd: HWND; Reason: LPCWSTR): Bool; stdcall; external user32;
function ShutdownBlockReasonDestroy(hWnd: HWND): Bool; stdcall; external user32;

 

Share this post


Link to post

Yes I found it informative but I get a "E2169 Field definition not allowed after methods or properties" error pointing at the external user32;   😞

type
  TForm3 = class(TForm)
    procedure FormShow(Sender: TObject);
    procedure WMQueryEndSession(var Msg: TWMQueryEndSession);
    message WM_QueryEndSession; // detect Windows shutdown message
    procedure SaveData; // routine to save data to disk
    function ShutdownBlockReasonCreate(hWnd: HWND; Reason: LPCWSTR): Bool; stdcall; external user32;
    function ShutdownBlockReasonDestroy(hWnd: HWND): Bool; stdcall; external user32;

The following is as I understand it att..

procedure TForm3.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
  if DataToBeSaved then
  begin
    Msg.Result := lResult(True); // disallow Windows from shutting down
    SaveData;
  end;
  Msg.Result := lResult(False); // allow Windows shutdown
end;

// Set DataToBeSaved to False after saving data
procedure TForm3.FormShow(Sender: TObject);
begin
  DataToBeSaved := True;
end;

procedure TForm3.SaveData;
begin
  ShutdownBlockReasonCreate(Application.MainForm.Handle, 'please wait while muting...');
  // Save data to disk files
  // ...
  beep;
  beep;
  beep;
  DataToBeSaved := False;
  //
  ShutdownBlockReasonDestroy(Application.MainForm.Handle);
end;

 

Share this post


Link to post
Posted (edited)

Ahhhh.  Ha! Testing..

Edited by Ian Branch

Share this post


Link to post
Posted (edited)

Hmmm.  OK.  I still have something amiss/NQR.  On telling the PC to shutdown with the App running Windows immediately goes to the Shutting down screen.

Reminder, I am on a Win 10 PC.

This is my full code att..

unit Unit3;

interface

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

type
  TForm3 = class(TForm)
    procedure FormShow(Sender: TObject);
    //
  private
    { Private declarations }
    procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QueryEndSession; // detect Windows shutdown message
    procedure SaveData; // routine to save data to disk
  public
    { Public declarations }
  end;

function ShutdownBlockReasonCreate(Handle: hWnd; Reason: LPCWSTR): Bool; stdcall; external user32;
function ShutdownBlockReasonDestroy(Handle: hWnd): Bool; stdcall; external user32;

var
  Form3: TForm3;
  DataToBeSaved: Boolean;

implementation

{$R *.dfm}

procedure TForm3.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
  if DataToBeSaved then
  begin
    Msg.Result := lResult(True); // disallow Windows from shutting down
    SaveData;
  end;
  Msg.Result := lResult(False); // allow Windows shutdown
end;

// Set DataToBeSaved to False after saving data
procedure TForm3.FormShow(Sender: TObject);
begin
  DataToBeSaved := True;
end;

procedure TForm3.SaveData;
begin
  ShutdownBlockReasonCreate(Application.MainForm.Handle, 'please wait while muting...');
  // Save data to disk files
  // ...
  beep;
  beep;
  beep;
  DataToBeSaved := False;
  //
  ShutdownBlockReasonDestroy(Application.MainForm.Handle);
end;

end.

At least it builds now. 😉

Edited by Ian Branch

Share this post


Link to post
24 minutes ago, Ian Branch said:

procedure TForm3.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
  if DataToBeSaved then
  begin
    Msg.Result := lResult(True); // disallow Windows from shutting down
    SaveData;
  end;
  Msg.Result := lResult(False); // allow Windows shutdown
end;

 

That's not how you do it.

You need to let the message handler return with the status you've set, and then call SaveData.

 

You also seem to be confused about the meaning of the WM_QUERYENDSESSION result value. False means you wish to postpone the shutdown.

 

Something like this:

const
  MSG_SAVEDATA = WM_USER;

type
  TForm3 = class(TForm)
  ...
  protected
    procedure MsgSaveData(var Msg: TMessage); message MSG_SAVEDATA;
    ...
  end;

procedure TForm3.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
  Msg.Result := lResult(not DataToBeSaved);
  if (DataToBeSaved) then
    PostMessage(Handle, MSG_SAVEDATA, 0, 0);
end;

procedure TForm3.MsgSaveData(var Msg: TMessage);
begin
  SaveData;
end;

 

Share this post


Link to post
Posted (edited)

The WM_QUERYENDSESSION documentation clearly states that data saving should be deferred to the WM_ENDSESSION message, so you don't really need the MSG_SAVEDATA approach:

Quote

When an application returns TRUE for this message, it receives the WM_ENDSESSION message, regardless of how the other applications respond to the WM_QUERYENDSESSION message. Each application should return TRUE or FALSE immediately upon receiving this message, and defer any cleanup operations until it receives the WM_ENDSESSION message.

And the WM_ENDSESSION documentation says:

Quote

Applications that have unsaved data could save the data to a temporary location and restore it the next time the application starts. It is recommended that applications save their data and state frequently; for example, automatically save data between save operations initiated by the user to reduce the amount of data to be saved at shutdown.

System shutdown can be aborted, in which case there would be no need to save any unsaved data, as long as your app has not been terminated yet.  WM_QUERYENDSESSION is just asking for permission for the system to be shut down.  The actual shutdown has not taken place yet.  But, once an actual shutdown begins, unsaved data should be saved automatically.  If you want to prompt the user whether unsaved data should be saved, do that in response to WM_CLOSE (or the TForm.OnCloseQuery event) instead:

Quote

An application can prompt the user for confirmation, prior to destroying a window, by processing the WM_CLOSE message and calling the DestroyWindow function only if the user confirms the choice.

If you receive WM_ENDSESSION before receiving WM_CLOSE/OnCloseQuery then simply don't prompt the user.

Edited by Remy Lebeau

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×