0
votes

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.
1
Please show us the exact command line you are sending to this app. Have you stepped through your code to see whether FAction gets assigned appropriately? I'm not familiar with TCommandLineParameterList but it could be that you're expected to put - or / in front of each param. According to your code, if no parameters are found, default behavior is to log off. - Jerry Dodge
Just added the command line prompt that get's read. - MrPeachyPenguin
Have a look at shutdown.exe and its parameters. No need to reinvent the wheel... - Delphi Coder
@MrPeachyPenguin it is the responsibility of the individual apps to react to a system shutdown properly and exit themselves as soon as possible - Remy Lebeau

1 Answers

3
votes

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;