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.
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;