delphicmdwindows-10shutdowndelphi-10.1-berlin

Terminating open applications &Powering Down the machine - Delphi Win10 question


Currently, I am creating an application in Delphi that will allow for the computer to fully power off after closing down open applications. Currently I am running into issues where the code isn't functioning properly in Win10 enterprise and it's logging the user out, but not shutting down the PC fully. the brains of the code is below, and I'm unclear why this would just log me out and not fully shut the computer down.

The user will define the params to run via a CMD to shut down the computer referencing the application. The application will shut down processes that are open and pass the shutdown process via the CMD.

procedure TfrmCleanShutdown.ProcessCommandLine;
const
  CMD_CAPTION   = 'w';  // window capShuttion
  CMD_CLASSNAME = 'c';  // window class name
  CMD_ACTION    = 'a';
  CMD_TIMEOUT   = 't';

  S_SHUTDOWN    = 'shutdown';
  S_LOGOFF      = 'logoff';
  S_RESTART     = 'restart';

var
  cmdl: TCommandLineParameterList;
  cmd: TCommandLineParameter;


begin
  cmdl := TCommandLineParameterList.Create;
  cmdl.Initialize;

  cmd := cmdl.FindByParam(CMD_CAPTION);
  if Assigned(cmd) then
    FWaitForWindowCaption := cmd.Param
  else
    FWaitForWindowCaption := '';

  cmd := cmdl.FindByParam(CMD_CLASSNAME);
  if Assigned(cmd) then
    FWaitForWindowClassName := cmd.Param
  else
    FWaitForWindowClassName := '';

  cmd := cmdl.FindByParam(CMD_TIMEOUT);
  if Assigned(cmd) then
    FTimeout := StrToIntDef(cmd.Param, DEF_TIMEOUT)
  else
    FTimeout := DEF_TIMEOUT;
  FAction := EWX_LOGOFF or EWX_FORCEIFHUNG; // default action
  cmd := cmdl.FindByParam(CMD_ACTION);
  if Assigned(cmd) then begin
    if cmd.Param = S_SHUTDOWN then
      FAction := EWX_POWEROFF or EWX_FORCEIFHUNG
    else if cmd.Param = S_RESTART then
      FAction := EWX_REBOOT or EWX_FORCEIFHUNG
  end;
end;

procedure TfrmCleanShutDown.ThreadTerminate(Sender: TObject);
begin
  if (Sender as TWaitForWindowCloseThread).Success then
    ExitWindowsEx(FAction, 0)
  else
    Label1.Caption := 'Timeout';
end;

Any help would be appreciated. I am unclear why win10 would behave in a way to just log off a user instead of shutdown, since the application is being run via a CMD and the user has shutdown rights. It's very confusing.

adding the command line code from a comment below.

unit uCommandLine;

interface

uses
  SysUtils, Classes, Contnrs;

const
  S_SWITCHES = '+-*';

type
  //======================================
  // TCommandLineParameter
  //======================================
  TCommandLineParameter = class
  private
    FParam: String;
    FSwitch: String;
    FOptions: String;
  public
    property Param: String read FParam write FParam;
    property Switch: String read FSwitch write FSwitch;
    property Options: String read FOptions write FOptions;
  end;  // TCommandLineParameter

  //======================================
  // TCommandLineParameterList
  //======================================
  TCommandLineParameterList = class(TObjectList)
  private
    function GetParameter(idx: Integer): TCommandLineParameter;
  public
    function Initialize: Boolean;
    function FindByParam(const ParamString: String): TCommandLineParameter;
    property Parameters[idx: Integer]: TCommandLineParameter read GetParameter;
  end;

implementation

const
  C_CMD_DELIM: Char = '-';

//======================================
// TCommandLineParameter
//======================================

//======================================
// TCommandLineParameterList
//======================================
//------------------------------------------------------------------------------
function TCommandLineParameterList.GetParameter(idx: Integer): TCommandLineParameter;
begin
  Result := TCommandLineParameter(Items[idx]);
end;

//------------------------------------------------------------------------------
function TCommandLineParameterList.Initialize: Boolean;
var
  n, idx: Integer;
  p: TCommandLineParameter;
  s: String;
begin
  Result := True;
  p := nil;
  n := ParamCount;
  for idx := 1 to n do begin
    s := Trim(ParamStr(idx));
    if s[1] = C_CMD_DELIM then begin
      System.Delete(s, 1, 1);
      p := FindByParam(s);
      if p = nil then begin
        p := TCommandLineParameter.Create;
        p.Param := s;
        Add(p);
      end
      else begin
        Result := False;
        break;
      end;
    end
    else begin
      if p <> nil then begin
        p.Options := s;  
        p := nil;
      end
      else begin
        Result := False;
        break;
      end;
    end;
  end;
end;

//------------------------------------------------------------------------------
function TCommandLineParameterList.FindByParam(const ParamString: String): TCommandLineParameter;
var
  idx: Integer;
begin
  Result := nil;
  for idx := 0 to Count-1 do
    if CompareStr(Parameters[idx].Param, ParamString) = 0 then begin
      Result := Parameters[idx];
      break;
    end;
end;
end.

Solution

  • You are not processing the command line parameters correctly in your ProcessCommandLine() method.

    When you call your app with this command line:

    -w -a shutdown -t 45

    FindByParam(CMD_ACTION) returns a TCommandLineParameter whose Param is 'a' and Options is 'shutdown'. But you are then looking for 'shutdown' in the Param instead of in the Options. You don't find a match, so you end up calling ExitWindowsEx() with your default EWX_LOGOFF flag instead of the intended EWX_POWEROFF flag.

    Had to debugged your code, you would have seen that happening.

    You are making the same mistake with all of your commands.

    Use this instead:

    procedure TfrmCleanShutdown.ProcessCommandLine;
    const
      CMD_CAPTION   = 'w'; // window capShuttion
      CMD_CLASSNAME = 'c'; // window class name
      CMD_ACTION    = 'a';
      CMD_TIMEOUT   = 't';
    
      S_SHUTDOWN    = 'shutdown';
      S_LOGOFF      = 'logoff';
      S_RESTART     = 'restart';
    
    var
      cmdl: TCommandLineParameterList;
      cmd: TCommandLineParameter;
    begin
      cmdl := TCommandLineParameterList.Create;
      try
        cmdl.Initialize;
    
        cmd := cmdl.FindByParam(CMD_CAPTION);
        if Assigned(cmd) then
          FWaitForWindowCaption := cmd.Options
        else
          FWaitForWindowCaption := '';
    
        cmd := cmdl.FindByParam(CMD_CLASSNAME);
        if Assigned(cmd) then
          FWaitForWindowClassName := cmd.Options
        else
          FWaitForWindowClassName := '';
    
        cmd := cmdl.FindByParam(CMD_TIMEOUT);
        if Assigned(cmd) then
          FTimeout := StrToIntDef(cmd.Options, DEF_TIMEOUT)
        else
          FTimeout := DEF_TIMEOUT;
    
        FAction := EWX_LOGOFF or EWX_FORCEIFHUNG; // default action
        cmd := cmdl.FindByParam(CMD_ACTION);
        if Assigned(cmd) then
        begin
          if cmd.Options = S_SHUTDOWN then
            FAction := EWX_POWEROFF or EWX_FORCEIFHUNG
          else if cmd.Options = S_RESTART then
            FAction := EWX_REBOOT or EWX_FORCEIFHUNG
        end;
      finally
        cmdl.Free;
      end;
    end; 
    

    That being said, your TCommandLineParameterList is completely redundant and can be removed, as the RTL's SysUtils.FindCmdLineSwitch() function can do all the same work for you, eg:

    uses
      ..., System.SysUtils;
    
    procedure TfrmCleanShutdown.ProcessCommandLine;
    const
      CMD_CAPTION   = 'w'; // window capShuttion
      CMD_CLASSNAME = 'c'; // window class name
      CMD_ACTION    = 'a';
      CMD_TIMEOUT   = 't';
    
      S_SHUTDOWN    = 'shutdown';
      S_LOGOFF      = 'logoff';
      S_RESTART     = 'restart';
    
    var
      Value: string;
    begin
      if FindCmdLineSwitch(CMD_CAPTION, Value) then
        FWaitForWindowCaption := Value
      else
        FWaitForWindowCaption := '';
    
      if FindCmdLineSwitch(CMD_CLASSNAME, Value) then
        FWaitForWindowClassName := Value
      else
        FWaitForWindowClassName := '';
    
      if FindCmdLineSwitch(CMD_TIMEOUT, Value) then
        FTimeout := StrToIntDef(Value, DEF_TIMEOUT)
      else
        FTimeout := DEF_TIMEOUT;
    
      FAction := EWX_LOGOFF or EWX_FORCEIFHUNG; // default action
      if FindCmdLineSwitch(CMD_ACTION, Value) then
      begin
        if Value = S_SHUTDOWN then
          FAction := EWX_POWEROFF or EWX_FORCEIFHUNG
        else if Value = S_RESTART then
          FAction := EWX_REBOOT or EWX_FORCEIFHUNG
      end;
    end;