{ ============================================
  Software Name : 	GAlertService
  ============================================ }
{ ******************************************** }
{ Written By WalWalWalides                     }
{ CopyRight © 2020                             }
{ Email : WalWalWalides@gmail.com              }
{ GitHub :https://github.com/walwalwalides     }
{ ******************************************** }

unit WinTask;

interface

uses
  Winapi.Windows, System.Classes, System.SysUtils, System.Contnrs, TaskSchedApi;

const
  BitMask: array [1 .. 16] of word = (1, 2, 4, 8, $10, $20, $40, $80, $100,
    $200, $400, $800, $1000, $2000, $4000, $8000);

  NotAvailOnXp = $20001322;

type
  TWinTaskStatus = (tsUnknown, tsReady, tsQueued, tsRunning, tsDisabled);
  TWinTaskCompatibility = (tcAT, tcXP, tcVista, tcWin7, tcWin10);
  TWinTaskActionType = (taExec, taCom, taMail, taMessage);
  TWinTaskTriggerType = (ttEvent, ttTime, ttDaily, ttWeekly, ttMonthly,
    ttMonthlyDow, ttIdle, ttRegistration, ttBoot, ttLogon, ttSessionStateChange,
    ttCustom);
  TLogonType = (ltNone, ltPassword, ltS4U, ltToken, ltGroup, ltService,
    ltTokenPassword);

  TWinServiceState = (ssUnknown, // Just fill the value 0
    ssStopped, // SERVICE_STOPPED
    ssStartPending, // SERVICE_START_PENDING
    ssStopPending, // SERVICE_STOP_PENDING
    ssRunning, // SERVICE_RUNNING
    ssContinuePending, // SERVICE_CONTINUE_PENDING
    ssPausePending, // SERVICE_PAUSE_PENDING
    ssPaused); // SERVICE_PAUSED

  TTimeInfo = record
    DateTime: TDateTime;
    TimeZone: boolean;
    // TimeZoneOffset : integer; // time zone offset in minutes
  end;

  TWinTask = class;

  TWinTaskAction = class(TCollectionItem)
  private
    pAction: IAction;
    FActionType: TWinTaskActionType;
    function GetActionType: TWinTaskActionType;
    function GetActionTypeAsString: string;
    function GetId: string;
    procedure SetId(Value: string);
  public
    destructor Destroy; override;
    procedure SetTaskAction(const Value: IAction);
    property ActionType: TWinTaskActionType read GetActionType;
    property ActionTypeAsString: string read GetActionTypeAsString;
    property Id: string read GetId write SetId;
    property TaskAction: IAction read pAction;
  end;

  TWinTaskExecAction = class(TWinTaskAction)
  private
    function GetPath: string;
    procedure SetPath(Value: string);
    function GetArguments: string;
    procedure SetArguments(Value: string);
    function GetWorkingDirectory: string;
    procedure SetWorkingDirectory(Value: string);
  public
    property ApplicationPath: string read GetPath write SetPath;
    property Arguments: string read GetArguments write SetArguments;
    property WorkingDirectory: string read GetWorkingDirectory
      write SetWorkingDirectory;
  end;

  TWinTaskComHandlerAction = class(TWinTaskAction)
  private
    function GetClassId: string;
    procedure SetClassId(Value: string);
    function GetData: string;
    procedure SetData(Value: string);
  public
    property ClassId: string read GetClassId write SetClassId;
    property Data: string read GetData write SetData;
  end;

  TWinTaskMailAction = class(TWinTaskAction)
  private
    function GetServer: string;
    function GetSubject: string;
    function GetTo: string;
    function GetCc: string;
    function GetBcc: string;
    function GetReplyTo: string;
    function GetFrom: string;
    function GetBody: string;
    // function GetHeaderFields
    // function GetAttachments
  public
    // property Attachments: PSafeArray read GetAttachments write SetAttachments;
    property Bcc: string read GetBcc; // write SetBcc;
    property Body: string read GetBody; // write SetBody;
    property Cc: string read GetCc; // write SetCc;
    property From: string read GetFrom; // write SetFrom;
    // property HeaderFields: ITaskNamedValueCollection read GetHeaderFields write SetHeaderFields;
    property ReplyTo: string read GetReplyTo; // write SetReplyTo;
    property Server: string read GetServer; // write SetServer;
    property Subject: string read GetSubject; // write SetSubject;
    property To_: string read GetTo; // write SetTo;
  end;

  TWinTaskMessageAction = class(TWinTaskAction)
  private
    function GetTitle: string;
    procedure SetTitle(Value: string);
    function GetMessageBody: string;
    procedure SetMessageBody(Value: string);
  public
    property Title: String read GetTitle write SetTitle;
    property MessageBody: String read GetMessageBody write SetMessageBody;
  end;

  TWinTaskActions = class(TCollection)
  private
    FTask: TWinTask;
    function GetItem(Index: Integer): TWinTaskAction;
    procedure SetItem(Index: Integer; Value: TWinTaskAction);
  public
    constructor Create(ATask: TWinTask);
    function New(AActionType: TWinTaskActionType): TWinTaskAction;
    function Add(AAction: IAction): TWinTaskAction; overload;
    procedure ClearAll;
    procedure Remove(AIndex: Integer);
    property Items[Index: Integer]: TWinTaskAction read GetItem write SetItem;
  end;

  TWinTaskTrigger = class(TCollectionItem)
  private
    pTrigger: ITrigger;
    FTriggerType: TWinTaskTriggerType;
    FTimeZone: boolean;
    FTimeOffset: Integer; // time zone offset in minutes
    function TimeStringToSeconds(TimeString: string): cardinal;
    function SecondsToTimeString(Seconds: cardinal): string;

    function GetDaysOfWeek: Integer;
    procedure SetDaysOfWeek(Value: Integer);
    function GetWeeksInterval: Integer;
    procedure SetWeeksInterval(Value: Integer);
    function GetWeeksOfMonth: Integer;
    procedure SetWeeksOfMonth(Value: Integer);
    function GetDaysOfMonth: Integer;
    procedure SetDaysOfMonth(Value: Integer);
    function GetMonthsOfYear: Integer;
    procedure SetMonthsOfYear(Value: Integer);
    function GetRunOnLastDayOfMonth: boolean;
    procedure SetRunOnLastDayOfMonth(Value: boolean);
    function GetRunOnLastWeekOfMonth: boolean;
    procedure SetRunOnLastWeekOfMonth(Value: boolean);
    function GetDaysInterval: SmallInt;
    procedure SetDaysInterval(Value: SmallInt);
    function GetDelay: cardinal; // time in seconds
    procedure SetDelay(Value: cardinal);
    function GetDuration: cardinal; // time in seconds
    procedure SetDuration(Value: cardinal);
    function GetExecutionTimeLimit: cardinal; // time in seconds
    procedure SetExecutionTimeLimit(Value: cardinal);
    function GetEndTime: TDateTime;
    procedure SetEndTime(Value: TDateTime);
    function GetEnabled: boolean;
    procedure SetEnabled(Value: boolean);
    function GetId: string;
    procedure SetId(Value: string);
    function GetLogonUserId: string;
    procedure SetLogonUserId(Value: string);
    function GetInterval: cardinal; // time in seconds
    procedure SetInterval(Value: cardinal);
    function GetRandomDelay: cardinal; // time in seconds
    procedure SetRandomDelay(Value: cardinal);
    function GetStartTime: TDateTime;
    procedure SetStartTime(Value: TDateTime);
    function GetStopAtDurationEnd: boolean;
    procedure SetStopAtDurationEnd(Value: boolean);
    function GetSubscription: string;
    procedure SetSubscription(Value: string);
    function GetTriggerType: TWinTaskTriggerType;
    function GetTriggerTypeAsString: string;
    function GetTriggerString: String;
  public
    constructor Create(Collection: TCollection);
    destructor Destroy; override;
    procedure SetTaskTrigger(const Value: ITrigger);
    procedure SetToDefault;
    // property Repetition: IRepetitionPattern read GetRepetition write Set_Repetition;
    property Delay: cardinal read GetDelay write SetDelay;
    property DaysInterval: SmallInt read GetDaysInterval write SetDaysInterval;
    property DaysOfMonth: Integer read GetDaysOfMonth write SetDaysOfMonth;
    property DaysOfWeek: Integer read GetDaysOfWeek write SetDaysOfWeek;
    property Duration: cardinal read GetDuration write SetDuration;
    property ExecutionTimeLimit: cardinal read GetExecutionTimeLimit
      write SetExecutionTimeLimit;
    property EndTime: TDateTime read GetEndTime write SetEndTime;
    property Enabled: boolean read GetEnabled write SetEnabled;
    property Id: string read GetId write SetId;
    property Interval: cardinal read GetInterval write SetInterval;
    property LogonUserId: string read GetLogonUserId write SetLogonUserId;
    property MonthsOfYear: Integer read GetMonthsOfYear write SetMonthsOfYear;
    property RandomDelay: cardinal read GetRandomDelay write SetRandomDelay;
    property RunOnLastDayOfMonth: boolean read GetRunOnLastDayOfMonth
      write SetRunOnLastDayOfMonth;
    property RunOnLastWeekOfMonth: boolean read GetRunOnLastWeekOfMonth
      write SetRunOnLastWeekOfMonth;
    property StartTime: TDateTime read GetStartTime write SetStartTime;
    property StopAtDurationEnd: boolean read GetStopAtDurationEnd
      write SetStopAtDurationEnd;
    property Subscription: string read GetSubscription write SetSubscription;
    property TaskTrigger: ITrigger read pTrigger;
    property TriggerType: TWinTaskTriggerType read GetTriggerType;
    property TriggerTypeAsString: string read GetTriggerTypeAsString;
    property TriggerString: String read GetTriggerString;
    property UseTimeZone: boolean read FTimeZone write FTimeZone;
    property WeeksInterval: Integer read GetWeeksInterval
      write SetWeeksInterval;
    property WeeksOfMonth: Integer read GetWeeksOfMonth write SetWeeksOfMonth;
  end;

  TWinTaskTriggers = class(TCollection)
  private
    FTask: TWinTask;
    function GetItem(Index: Integer): TWinTaskTrigger;
    procedure SetItem(Index: Integer; Value: TWinTaskTrigger);
  public
    constructor Create(ATask: TWinTask);
    function New(ATriggerType: TWinTaskTriggerType): TWinTaskTrigger;
    function Add(ATrigger: ITrigger): TWinTaskTrigger; overload;
    procedure ClearAll;
    procedure Remove(AIndex: Integer);
    property Items[Index: Integer]: TWinTaskTrigger read GetItem write SetItem;
  end;

  TWinTask = class(TObject)
  private
    pDefinition: ITaskDefinition;
    FData: TMemoryStream;
    FActions: TWinTaskActions;
    FSelectedAction: Integer;
    FTriggers: TWinTaskTriggers;
    function GetAuthor: string;
    procedure SetAuthor(Value: string);
    function GetData: string;
    procedure SetData(Value: string);
    function GetDate: TDateTime;
    function GetDateAsString: string;
    procedure SetDate(Value: TDateTime);
    function GetDescription: string;
    procedure SetDescription(Value: string);
    function GetDocumentation: string;
    procedure SetDocumentation(Value: string);
    function GetDisplayName: string;
    procedure SetDisplayName(Value: string);
    function GetGroupId: string;
    procedure SetGroupId(Value: string);
    function GetId: string;
    procedure SetId(Value: string);
    function GetUserId: string;
    procedure SetUserId(Value: string);
    function GetLogonType: TLogonType;
    procedure SetLogonType(Value: TLogonType);
    function GetCompatibility: TWinTaskCompatibility;
    procedure SetCompatibility(Value: TWinTaskCompatibility);
    function GetCompatibilityAsString: string;
    function GetRunIfMissed: boolean;
    procedure SetRunIfMissed(Value: boolean);
    function GetAction(Index: Integer): TWinTaskAction;
    function GetActionCount: Integer;
    function GetTrigger(Index: Integer): TWinTaskTrigger;
    function GetTriggerCount: Integer;
  public
    constructor Create(const ADefinition: ITaskDefinition);
    destructor Destroy; override;
    function NewAction(AActionType: TWinTaskActionType): TWinTaskAction;
    function DeleteAction(AIndex: Integer): boolean;
    function NewTrigger(ATriggerType: TWinTaskTriggerType): TWinTaskTrigger;
    function DeleteTrigger(AIndex: Integer): boolean;
    procedure Refresh;
    function IndexOfApp(const AppFilter: string): Integer;
    function IndexOfNextExecAction(FromIndex: Integer = 0): Integer;
    property Actions[Index: Integer]: TWinTaskAction read GetAction;
    property ActionCount: Integer read GetActionCount;
    property Author: string read GetAuthor write SetAuthor;
    property Compatibility: TWinTaskCompatibility read GetCompatibility
      write SetCompatibility;
    property CompatibilityAsString: string read GetCompatibilityAsString;
    property UserData: string read GetData write SetData;
    property Date: TDateTime read GetDate write SetDate;
    property DateAsString: string read GetDateAsString;
    property Description: string read GetDescription write SetDescription;
    property DisplayName: string read GetDisplayName write SetDisplayName;
    property Documentation: string read GetDocumentation write SetDocumentation;
    property GroupId: string read GetGroupId write SetGroupId;
    property Id: string read GetId write SetId;
    property LogonType: TLogonType read GetLogonType write SetLogonType;
    property RunIfMissed: boolean read GetRunIfMissed write SetRunIfMissed;
    property SelectedAction: Integer read FSelectedAction;
    property Triggers[Index: Integer]: TWinTaskTrigger read GetTrigger;
    property TriggerCount: Integer read GetTriggerCount;
    property UserId: string read GetUserId write SetUserId;
  end;

  TWinRegisteredTask = class(TObject)
  private
    pRegisteredTask: IRegisteredTask;
    FTaskName: String;
    FIndex: Integer;
    FTask: TWinTask;
    function GetEnabled: boolean;
    procedure SetEnabled(Value: boolean);
    function GetTaskResult: Integer;
    function GetLastRunTime: TDateTime;
    function GetLastRunTimeAsString: string;
    function GetNextRunTime: TDateTime;
    function GetNextRunTimeAsString: string;
    function GetNumberOfMissedRuns: Integer;
    function GetPath: string;
    function GetStatus: TWinTaskStatus;
    function GetStatusAsString: string;
  public
    constructor Create(const ARegisteredTask: IRegisteredTask);
    destructor Destroy; override;
    procedure Refresh;
    procedure Run;
    procedure Stop;
    function IndexOfApp(const AppFilter: string): Integer;
    property Definition: TWinTask read FTask;
    property Enabled: boolean read GetEnabled write SetEnabled;
    property LastResult: Integer read GetTaskResult;
    property LastRunTime: TDateTime read GetLastRunTime;
    property LastRunTimeAsString: string read GetLastRunTimeAsString;
    property NextRunTime: TDateTime read GetNextRunTime;
    property NextRunTimeAsString: string read GetNextRunTimeAsString;
    property NumberOfMissedRuns: Integer read GetNumberOfMissedRuns;
    property Path: string read GetPath;
    property Status: TWinTaskStatus read GetStatus;
    property StatusAsString: string read GetStatusAsString;
    property TaskIndex: Integer read FIndex write FIndex;
    property TaskName: string read FTaskName;
  end;

  TWinTaskList = class(TObjectList)
  public
    // function Add (ARegTask : TWinRegisteredTask) : integer;
  end;

  TWinTaskFolder = class(TObject)
  private
    pRootFolder: ITaskFolder;
    pTaskCollection: IRegisteredTaskCollection;
    FNumTasks: LONG;
    FTasks: TWinTaskList;
    FErrMsg: string;
    FErrorCode: cardinal;
    function GetTask(Index: Integer): TWinRegisteredTask;
  public
    constructor Create(ARootFolder: ITaskFolder);
    destructor Destroy; override;
    function Refresh: HResult;
    function DeleteTask(const TaskName: string): cardinal;
    function IndexOf(const TaskName: String): Integer;
    function RegisterTask(const TaskName: string; ATask: TWinTask;
      Username, Password: string): Integer;
    property ErrorCode: cardinal read FErrorCode;
    property ErrorMessage: string read FErrMsg;
    property NumberOfTasks: Integer read FNumTasks;
    property Tasks[Index: Integer]: TWinRegisteredTask read GetTask;
    property TaskCount: Integer read FNumTasks;
  end;

  // TWinTaskScheduler = class (TObject)
  TWinTaskScheduler = class(TObject)
  private
    pService: ITaskService;
    FFolder: string;
    FTaskFolder: TWinTaskFolder;
    FResult: HResult;
    procedure SetFolder(const AFolder: string);
  protected
  public
    constructor Create;
    destructor Destroy; override;
    function Init: HResult;
    function Refresh: HResult;
    function NewTask: TWinTask;
    property Folder: string read FFolder write SetFolder;
    property TaskFolder: TWinTaskFolder read FTaskFolder;
  public
    class function IsRunning: boolean;
  end;

function GetDefaultCompatibility: TWinTaskCompatibility;
function GetServiceStatusByName(const AServer, AServiceName: string)
  : TWinServiceState;

function CreateWinTaskScheduler(var TaskSchedule: TWinTaskScheduler): HResult;

implementation

uses System.Win.ComObj, System.DateUtils, System.Math, System.StrUtils,
  Winapi.ActiveX,
  Winapi.WinSvc, System.Variants, ExtSysUtils, WinTaskConsts
{$IFDEF Trace}
    , FileUtils
{$ENDIF}
    ;

const
  TaskTriggerNames: array [TWinTaskTriggerType] of string = ('Event', 'Time',
    'Daily', 'Weekly', 'Monthly', 'MonthlyDow', 'Idle', 'Register', 'Boot',
    'Logon', 'State', 'Custom');

var
  TaskStateNames: array [TWinTaskStatus] of pointer = (
    @rsTrgUnknown,
    @rsTrgReady,
    @rsTrgQueued,
    @rsTrgRunning,
    @rsTrgDisabled
  );

  TaskActionNames: array [TWinTaskActionType] of pointer = (
    @rsTrgActionExec,
    @rsTragActionCom,
    @rsTragActionMail,
    @rsTragActionMsg
  );

  TaskTriggerDescriptions: array [TWinTaskTriggerType] of pointer = (
    @rsTrgTypeEvent,
    @rsTrgTypeTime,
    @rsTrgTypeDaily,
    @rsTrgTypeWeekly,
    @rsTrgTypeMonthly,
    @rsTrgTypeMonthly,
    @rsTrgTypeIdle,
    @rsTrgTypeRegister,
    @rsTrgTypeStartup,
    @rsTrgTypeLogon,
    @rsTrgTypeStateChg,
    @rsTrgTypeCustom
  );

  TaskWeeksOfMonthList: array [1 .. 6] of pointer = (
    @rsTrgFirst,
    @rsTrgSecond,
    @rsTrgThird,
    @rsTrgFourth,
    @rsTrgFifth,
    @rsTrgLast
  );

  CompatibilityNames: array [TWinTaskCompatibility] of pointer = (
    @rsCompAT,
    @rsXP,
    @rsVista,
    @rsWin7,
    @rsWin10
  );

  TaskFlags: array [TLogonType] of LongWord = (
    TASK_LOGON_NONE,
    TASK_LOGON_PASSWORD,
    TASK_LOGON_S4U,
    TASK_LOGON_INTERACTIVE_TOKEN,
    TASK_LOGON_GROUP,
    TASK_LOGON_SERVICE_ACCOUNT,
    TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD
  );

  ActionTypes: array [TWinTaskActionType] of LongWord = (
    TASK_ACTION_EXEC,
    TASK_ACTION_COM_HANDLER,
    TASK_ACTION_SEND_EMAIL,
    TASK_ACTION_SHOW_MESSAGE
  );

  TriggerTypes: array [TWinTaskTriggerType] of LongWord = (
    TASK_TRIGGER_EVENT,
    TASK_TRIGGER_TIME,
    TASK_TRIGGER_DAILY,
    TASK_TRIGGER_WEEKLY,
    TASK_TRIGGER_MONTHLY,
    TASK_TRIGGER_MONTHLYDOW,
    TASK_TRIGGER_IDLE,
    TASK_TRIGGER_REGISTRATION,
    TASK_TRIGGER_BOOT,
    TASK_TRIGGER_LOGON,
    TASK_TRIGGER_SESSION_STATE_CHANGE,
    TASK_TRIGGER_CUSTOM_TRIGGER_01
  );

  Compatibilities: array [TWinTaskCompatibility] of LongWord = (
    TASK_COMPATIBILITY_AT,
    TASK_COMPATIBILITY_V1,
    TASK_COMPATIBILITY_V2,
    TASK_COMPATIBILITY_V2_1,
    TASK_COMPATIBILITY_V2_2
  );

  { ------------------------------------------------------------------- }
function GetServiceStatusByName(const AServer, AServiceName: string)
  : TWinServiceState;
var
  ServiceHandle, SCMHandle: DWORD;
  SCMAccess, Access: DWORD;
  ServiceStatus: TServiceStatus;
{$IFDEF Trace}
const
  WinServiceStates: array [0 .. 7] of string = ('Unknown',
    // Just fill the value 0
    'Stopped', // SERVICE_STOPPED
    'StartPending', // SERVICE_START_PENDING
    'StopPending', // SERVICE_STOP_PENDING
    'Running', // SERVICE_RUNNING
    'ContinuePending', // SERVICE_CONTINUE_PENDING
    'PausePending', // SERVICE_PAUSE_PENDING
    'Paused'); // SERVICE_PAUSED
{$ENDIF}
begin
  Result := ssUnknown;
  SCMAccess := SC_MANAGER_CONNECT or SC_MANAGER_ENUMERATE_SERVICE or
    SC_MANAGER_QUERY_LOCK_STATUS;
  Access := SERVICE_INTERROGATE or GENERIC_READ;
  SCMHandle := OpenSCManager(PChar(AServer), Nil, SCMAccess);
  if SCMHandle <> 0 then
  begin
    try
      ServiceHandle := OpenService(SCMHandle, PChar(AServiceName), Access);
      if ServiceHandle <> 0 then
      begin
        try
          ZeroMemory(@ServiceStatus, SizeOf(ServiceStatus));
          if QueryServiceStatus(ServiceHandle, ServiceStatus) then
          begin
            Result := TWinServiceState(ServiceStatus.dwCurrentState);
{$IFDEF Trace}
            WriteDebugLog(TryFormat('Status of service "%s": %s',
              [AServiceName, WinServiceStates[ServiceStatus.dwCurrentState]]));
{$ENDIF}
          end;
        finally
          CloseServiceHandle(ServiceHandle);
        end;
      end
{$IFDEF Trace}
      else
        WriteDebugLog(TryFormat('Error opening service "%s" - ', [AServiceName])
          + SysErrorMessage(GetLastError));
{$ENDIF}
    finally
      CloseServiceHandle(SCMHandle);
    end;
  end
{$IFDEF Trace}
  else
    WriteDebugLog('Error opening service mananger - ' +
      SysErrorMessage(GetLastError));
{$ENDIF}
end;

{ ------------------------------------------------------------------- }
// Read number (word) between next non numerical character,
function ReadNextValue(var sTime: string): word;
var
  n, k, v: Integer;
const
  Numbers = ['0' .. '9'];
begin
  if length(sTime) > 0 then
  begin
    n := 1;
    while not(sTime[n] in Numbers) do
      inc(n);
    k := n;
    while sTime[k] in Numbers do
      inc(k);
    if TryStrToInt(copy(sTime, n, k - n), v) then
      Result := v
    else
      Result := 0;
    Delete(sTime, 1, k - 1);
  end
  else
    Result := 0;
end;

function GetUnitNumber(const sOne, sMany: string; n: Integer): string;
begin
  if n = 1 then
    Result := TryFormat(sOne, [n])
  else
    Result := TryFormat(sMany, [n]);
end;

// Convert boundary string to TDateTime
function BoundaryToTimeInfo(Boundary: string): TTimeInfo; overload;
var
  y, m, d, h, n, s: Integer;
  minus: boolean;
begin
  with Result do
  begin
    DateTime := 0;
    TimeZone := false;
  end;
  if length(Boundary) = 0 then
    Exit;
  y := ReadNextValue(Boundary);
  m := ReadNextValue(Boundary);
  d := ReadNextValue(Boundary);
  h := ReadNextValue(Boundary);
  n := ReadNextValue(Boundary);
  s := ReadNextValue(Boundary);
  try
    Result.DateTime := EncodeDateTime(y, m, d, h, n, s, 0);
  except
    Exit;
  end;
  if (length(Boundary) > 0) then
  begin // offset
    Result.TimeZone := true;
    // if Boundary[1]='Z' then Result.TimeZoneOffset:=0
    // Result:=Result-TTimeZone.Local.UtcOffset.TotalMinutes/MinsPerDay
    // else with Result do begin
    // minus:=Boundary[1]='-';
    // h:=ReadNextValue(Boundary);
    // n:=ReadNextValue(Boundary);
    // TimeZoneOffset:=h*MinsPerHour+n;
    // if minus then TimeZoneOffset:=-TimeZoneOffset;
    // end;
    // if TTimeZone.Local.IsDaylightTime(Now) then Result:=Result+OneHour;
  end
  else
    with Result do
    begin
      TimeZone := false; // TimeZoneOffset:=0;
    end;
end;

function BoundaryToDateTime(Boundary: string): TDateTime; overload;
begin
  Result := BoundaryToTimeInfo(Boundary).DateTime;
end;

function DateTimeToBoundary(Value: TDateTime; UseTimeZone: boolean): string;
var
  m: double;
begin
  if Value < 1 then
    Result := ''
  else
  begin
    Result := FormatDateTime('yyyy-mm-dd"T"hh:nn:ss', Value);
    if UseTimeZone then
    begin
      m := TTimeZone.Local.UtcOffset.TotalMinutes;
      if m = 0 then
        Result := Result + 'Z'
      else
      begin
        if m < 0 then
          Result := Result + '-'
        else
          Result := Result + '+';
        Result := Result + FormatDateTime('hh:nn', abs(m) / MinsPerDay);
      end;
    end;
  end;
end;

function DateTimeToText(DateTime: TDateTime): string;
// only values from 2000-01-01
begin
  if DateTime < EncodeDate(2000, 1, 1) then
    Result := ''
  else
    Result := DateTimeToStr(DateTime);
end;

function GetDefaultCompatibility: TWinTaskCompatibility;
begin
  if (Win32Platform < VER_PLATFORM_WIN32_NT) then
    Result := tcAT
  else
  begin
    if (Win32MajorVersion = 5) then
      Result := tcXP
    else if (Win32MajorVersion < 10) then
    begin
      if (Win32MinorVersion = 0) then
        Result := tcVista
      else
        Result := tcWin7
    end
    else
      Result := tcWin10;
  end;
end;

{ ------------------------------------------------------------------- }
destructor TWinTaskAction.Destroy;
begin
  pAction := nil;
  inherited Destroy;
end;

procedure TWinTaskAction.SetTaskAction(const Value: IAction);
begin
  pAction := Value;
  FActionType := GetActionType;
  // if length(Id)=0 then SetId('Action'+IntToStr(Index+1));
end;

function TWinTaskAction.GetActionType: TWinTaskActionType;
begin
  case pAction.type_ of
    TASK_ACTION_COM_HANDLER:
      Result := taCom;
    TASK_ACTION_SEND_EMAIL:
      Result := taMail;
    TASK_ACTION_SHOW_MESSAGE:
      Result := taMessage;
  else
    Result := taExec; // TASK_ACTION_EXEC
  end;
end;

function TWinTaskAction.GetActionTypeAsString: string;
begin
  Result := LoadResString(TaskActionNames[ActionType]);
end;

function TWinTaskAction.GetId: string;
begin
  Result := pAction.Id;
end;

procedure TWinTaskAction.SetId(Value: string);
begin
  pAction.Id := Value;
end;

{ ------------------------------------------------------------------- }
function TWinTaskExecAction.GetPath: string;
begin
  Result := AnsiDequotedStr((pAction as IExecAction).Path, '"');
end;

procedure TWinTaskExecAction.SetPath(Value: string);
begin
  (pAction as IExecAction).Path := Value;
end;

function TWinTaskExecAction.GetArguments: string;
begin
  Result := (pAction as IExecAction).Arguments;
end;

procedure TWinTaskExecAction.SetArguments(Value: string);
begin
  (pAction as IExecAction).Arguments := Value;
end;

function TWinTaskExecAction.GetWorkingDirectory: string;
begin
  Result := (pAction as IExecAction).WorkingDirectory;
end;

procedure TWinTaskExecAction.SetWorkingDirectory(Value: string);
begin
  (pAction as IExecAction).WorkingDirectory := Value;
end;

{ ------------------------------------------------------------------- }
function TWinTaskComHandlerAction.GetClassId: string;
begin
  Result := (pAction as IComHandlerAction).ClassId;
end;

procedure TWinTaskComHandlerAction.SetClassId(Value: string);
begin
  (pAction as IComHandlerAction).ClassId := Value;
end;

function TWinTaskComHandlerAction.GetData: string;
begin
  Result := (pAction as IComHandlerAction).Data;
end;

procedure TWinTaskComHandlerAction.SetData(Value: string);
begin
  (pAction as IComHandlerAction).Data := Value;
end;

{ ------------------------------------------------------------------- }
function TWinTaskMailAction.GetServer: string;
begin
  Result := (pAction as IEMailAction).Server;
end;

function TWinTaskMailAction.GetSubject: string;
begin
  Result := (pAction as IEMailAction).Subject;
end;

function TWinTaskMailAction.GetTo: string;
begin
  Result := (pAction as IEMailAction).To_;
end;

function TWinTaskMailAction.GetCc: string;
begin
  Result := (pAction as IEMailAction).Cc;
end;

function TWinTaskMailAction.GetBcc: string;
begin
  Result := (pAction as IEMailAction).Bcc;
end;

function TWinTaskMailAction.GetReplyTo: string;
begin
  Result := (pAction as IEMailAction).ReplyTo;
end;

function TWinTaskMailAction.GetFrom: string;
begin
  Result := (pAction as IEMailAction).From;
end;

function TWinTaskMailAction.GetBody: string;
begin
  Result := (pAction as IEMailAction).Body;
end;

// function TWinTaskMailAction.GetHeaderFields
// function TWinTaskMailAction.GetAttachments

{ ------------------------------------------------------------------- }
function TWinTaskMessageAction.GetTitle: string;
begin
  Result := (pAction as IShowMessageAction).Title;
end;

procedure TWinTaskMessageAction.SetTitle(Value: string);
begin
  (pAction as IShowMessageAction).Title := Value;
end;

function TWinTaskMessageAction.GetMessageBody: string;
begin
  Result := (pAction as IShowMessageAction).MessageBody;
end;

procedure TWinTaskMessageAction.SetMessageBody(Value: string);
begin
  (pAction as IShowMessageAction).MessageBody := Value;
end;

{ ------------------------------------------------------------------- }
constructor TWinTaskActions.Create(ATask: TWinTask);
begin
  inherited Create(TWinTaskAction);
  FTask := ATask;
end;

function TWinTaskActions.GetItem(Index: Integer): TWinTaskAction;
begin
  Result := TWinTaskAction(inherited GetItem(Index));
end;

procedure TWinTaskActions.SetItem(Index: Integer; Value: TWinTaskAction);
begin
  inherited SetItem(Index, Value);
end;

function TWinTaskActions.New(AActionType: TWinTaskActionType): TWinTaskAction;
begin
  Result := TWinTaskAction(Add);
  Result.SetTaskAction(FTask.pDefinition.Actions.Create
    (ActionTypes[AActionType]));
end;

function TWinTaskActions.Add(AAction: IAction): TWinTaskAction;
begin
  Result := TWinTaskAction(Add);
  Result.SetTaskAction(AAction);
end;

procedure TWinTaskActions.ClearAll;
begin
  FTask.pDefinition.Actions.Clear;
  Clear;
end;

procedure TWinTaskActions.Remove(AIndex: Integer);
begin
  FTask.pDefinition.Actions.Remove(AIndex);
  Delete(AIndex);
end;

{ ------------------------------------------------------------------- }
constructor TWinTaskTrigger.Create(Collection: TCollection);
begin
  inherited Create(Collection);
  FTimeZone := false;
  FTimeOffset := 0;
end;

destructor TWinTaskTrigger.Destroy;
begin
  pTrigger := nil;
  inherited Destroy;
end;

procedure TWinTaskTrigger.SetToDefault;
begin
  StartTime := Now;
  // EndTime:=Now+DaysInAYear(YearOf(Now));
  // Interval:=SecsPerHour;
  // Duration:=SecsPerDay;
  // ExecutionTimeLimit:=3*SecsPerDay;
  // case TriggerType of
  // ttDaily : begin
  // RandomDelay:=SecsPerHour;
  // end;
  // end;
end;

procedure TWinTaskTrigger.SetTaskTrigger(const Value: ITrigger);
begin
  pTrigger := Value;
  FTriggerType := GetTriggerType;
  // if length(Id)=0 then SetId('Trigger'+IntToStr(Index+1));
  FTimeZone := BoundaryToTimeInfo(pTrigger.StartBoundary).TimeZone;
end;

function TWinTaskTrigger.TimeStringToSeconds(TimeString: string): cardinal;
var
  n: word;
begin
  Result := 0;
  if AnsiStartsText('P', TimeString) then
  begin
    Delete(TimeString, 1, 1);
    if AnsiStartsText('T', TimeString) then
    begin // time
      repeat
        n := ReadNextValue(TimeString);
        if AnsiStartsText('H', TimeString) then
          Result := Result + SecsPerHour * n;
        if AnsiStartsText('M', TimeString) then
          Result := Result + SecsPerMin * n;
        if AnsiStartsText('S', TimeString) then
          Result := Result + n;
        if length(TimeString) > 0 then
          Delete(TimeString, 1, 1);
      until length(TimeString) = 0;
    end
    else
    begin // days
      n := ReadNextValue(TimeString);
      if AnsiStartsText('D', TimeString) then
        Result := Result + SecsPerDay * n;
    end;
  end
end;

function TWinTaskTrigger.SecondsToTimeString(Seconds: cardinal): string;
var
  h, m, s: word;
begin
  if Seconds = 0 then
    Result := ''
  else if Seconds >= SecsPerDay then
    Result := 'P' + IntToStr(Seconds div SecsPerDay) + 'D'
  else
  begin
    Result := 'PT';
    DivMod(Seconds, SecsPerHour, h, m);
    DivMod(m, SecsPerMin, m, s);
    if h > 0 then
      Result := Result + IntToStr(h) + 'H';
    if m > 0 then
      Result := Result + IntToStr(m) + 'M';
    if s > 0 then
      Result := Result + IntToStr(s) + 'S';
  end;
end;

function TWinTaskTrigger.GetDaysOfWeek: Integer;
begin
  if FTriggerType = ttWeekly then
    Result := IWeeklyTrigger(pTrigger).DaysOfWeek
  else if FTriggerType = ttMonthlyDow then
    Result := IMonthlyDowTrigger(pTrigger).DaysOfWeek
  else
    Result := 0;
end;

procedure TWinTaskTrigger.SetDaysOfWeek(Value: Integer);
begin
  if (Value > 0) and (Value < $80) then
  begin
    if FTriggerType = ttWeekly then
      IWeeklyTrigger(pTrigger).DaysOfWeek := Value
    else if FTriggerType = ttMonthlyDow then
      IMonthlyDowTrigger(pTrigger).DaysOfWeek := Value;
  end;
end;

function TWinTaskTrigger.GetWeeksInterval: Integer;
begin
  if FTriggerType = ttWeekly then
    Result := IWeeklyTrigger(pTrigger).WeeksInterval
  else
    Result := 0;
end;

procedure TWinTaskTrigger.SetWeeksInterval(Value: Integer);
begin
  if (Value > 0) and (FTriggerType = ttWeekly) then
    IWeeklyTrigger(pTrigger).WeeksInterval := Value;
end;

function TWinTaskTrigger.GetWeeksOfMonth: Integer;
begin
  if FTriggerType = ttMonthlyDow then
    Result := IMonthlyDowTrigger(pTrigger).WeeksOfMonth
  else
    Result := 0;
end;

procedure TWinTaskTrigger.SetWeeksOfMonth(Value: Integer);
begin
  if (Value > 0) and (Value < $40) and (FTriggerType = ttMonthlyDow) then
    IMonthlyDowTrigger(pTrigger).WeeksOfMonth := Value;
end;

function TWinTaskTrigger.GetDaysOfMonth: Integer;
begin
  if FTriggerType = ttMonthly then
    Result := IMonthlyTrigger(pTrigger).DaysOfMonth
  else
    Result := 0;
end;

procedure TWinTaskTrigger.SetDaysOfMonth(Value: Integer);
begin
  if (Value > 0) and (FTriggerType = ttMonthly) then
    IMonthlyTrigger(pTrigger).DaysOfMonth := Value;
end;

function TWinTaskTrigger.GetMonthsOfYear: Integer;
begin
  if FTriggerType = ttMonthly then
    Result := IMonthlyTrigger(pTrigger).MonthsOfYear
  else if FTriggerType = ttMonthlyDow then
    Result := IMonthlyDowTrigger(pTrigger).MonthsOfYear
  else
    Result := 0;
end;

procedure TWinTaskTrigger.SetMonthsOfYear(Value: Integer);
begin
  if (Value > 0) and (Value < $1000) then
  begin
    if FTriggerType = ttMonthly then
      IMonthlyTrigger(pTrigger).MonthsOfYear := Value
    else if FTriggerType = ttMonthlyDow then
      IMonthlyDowTrigger(pTrigger).MonthsOfYear := Value;
  end;
end;

function TWinTaskTrigger.GetRunOnLastDayOfMonth: boolean;
begin
  if FTriggerType = ttMonthly then
    Result := IMonthlyTrigger(pTrigger).RunOnLastDayOfMonth
  else
    Result := false;
end;

procedure TWinTaskTrigger.SetRunOnLastDayOfMonth(Value: boolean);
begin
  if FTriggerType = ttMonthly then
    IMonthlyTrigger(pTrigger).RunOnLastDayOfMonth := Value;
end;

function TWinTaskTrigger.GetRunOnLastWeekOfMonth: boolean;
begin
  if FTriggerType = ttMonthlyDow then
    Result := IMonthlyDowTrigger(pTrigger).RunOnLastWeekOfMonth
  else
    Result := false;
end;

procedure TWinTaskTrigger.SetRunOnLastWeekOfMonth(Value: boolean);
begin
  if FTriggerType = ttMonthlyDow then
    IMonthlyDowTrigger(pTrigger).RunOnLastWeekOfMonth := Value
end;

function TWinTaskTrigger.GetRandomDelay: cardinal; // time in seconds
var
  st: string;
begin
  case FTriggerType of
    ttTime:
      Result := TimeStringToSeconds(ITimeTrigger(pTrigger).RandomDelay);
    ttDaily:
      Result := TimeStringToSeconds(IDailyTrigger(pTrigger).RandomDelay);
    ttWeekly:
      Result := TimeStringToSeconds(IWeeklyTrigger(pTrigger).RandomDelay);
    ttMonthly:
      Result := TimeStringToSeconds(IMonthlyTrigger(pTrigger).RandomDelay);
    ttMonthlyDow:
      Result := TimeStringToSeconds(IMonthlyDowTrigger(pTrigger).RandomDelay);
  else
    Result := 0;
  end;
end;

procedure TWinTaskTrigger.SetRandomDelay(Value: cardinal);
var
  st: string;
begin
  st := SecondsToTimeString(Value);
  case FTriggerType of
    ttTime:
      ITimeTrigger(pTrigger).RandomDelay := st;
    ttDaily:
      IDailyTrigger(pTrigger).RandomDelay := st;
    ttWeekly:
      IWeeklyTrigger(pTrigger).RandomDelay := st;
    ttMonthly:
      IMonthlyTrigger(pTrigger).RandomDelay := st;
    ttMonthlyDow:
      IMonthlyDowTrigger(pTrigger).RandomDelay := st;
  end;
end;

function TWinTaskTrigger.GetDaysInterval: SmallInt;
begin
  if FTriggerType = ttDaily then
    Result := IDailyTrigger(pTrigger).DaysInterval
  else
    Result := 0;
end;

procedure TWinTaskTrigger.SetDaysInterval(Value: SmallInt);
begin
  if (Value > 0) and (FTriggerType = ttDaily) then
    IDailyTrigger(pTrigger).DaysInterval := Value;
end;

function TWinTaskTrigger.GetDelay: cardinal; // time in seconds
begin
  case FTriggerType of
    ttBoot:
      Result := TimeStringToSeconds(IBootTrigger(pTrigger).Delay);
    ttLogon:
      Result := TimeStringToSeconds(ILogonTrigger(pTrigger).Delay);
    ttEvent:
      Result := TimeStringToSeconds(IEventTrigger(pTrigger).Delay);
  else
    Result := 0;
  end;
end;

procedure TWinTaskTrigger.SetDelay(Value: cardinal);
var
  st: string;
begin
  st := SecondsToTimeString(Value);
  case FTriggerType of
    ttBoot:
      IBootTrigger(pTrigger).Delay := st;
    ttLogon:
      ILogonTrigger(pTrigger).Delay := st;
    ttEvent:
      IEventTrigger(pTrigger).Delay := st;
  end;
end;

function TWinTaskTrigger.GetDuration: cardinal; // time in seconds
begin
  Result := TimeStringToSeconds(pTrigger.Repetition.Duration);
end;

procedure TWinTaskTrigger.SetDuration(Value: cardinal);
begin
  pTrigger.Repetition.Duration := SecondsToTimeString(Value);
end;

function TWinTaskTrigger.GetInterval: cardinal; // time in seconds
begin
  Result := TimeStringToSeconds(pTrigger.Repetition.Interval);
end;

procedure TWinTaskTrigger.SetInterval(Value: cardinal);
begin
  pTrigger.Repetition.Interval := SecondsToTimeString(Value);
end;

function TWinTaskTrigger.GetStopAtDurationEnd: boolean;
begin
  Result := pTrigger.Repetition.StopAtDurationEnd;
end;

procedure TWinTaskTrigger.SetStopAtDurationEnd(Value: boolean);
begin
  pTrigger.Repetition.StopAtDurationEnd := Value;
end;

function TWinTaskTrigger.GetExecutionTimeLimit: cardinal; // time in seconds
begin
  Result := TimeStringToSeconds(pTrigger.ExecutionTimeLimit);
end;

procedure TWinTaskTrigger.SetExecutionTimeLimit(Value: cardinal);
begin
  pTrigger.ExecutionTimeLimit := SecondsToTimeString(Value);
end;

function TWinTaskTrigger.GetEndTime: TDateTime;
begin
  Result := BoundaryToDateTime(pTrigger.EndBoundary);
end;

procedure TWinTaskTrigger.SetEndTime(Value: TDateTime);
begin
  pTrigger.EndBoundary := DateTimeToBoundary(Value, FTimeZone);
end;

function TWinTaskTrigger.GetEnabled: boolean;
begin
  Result := pTrigger.Enabled;
end;

procedure TWinTaskTrigger.SetEnabled(Value: boolean);
begin
  pTrigger.Enabled := Value;
end;

function TWinTaskTrigger.GetId: string;
begin
  Result := pTrigger.Id;
end;

procedure TWinTaskTrigger.SetId(Value: string);
begin
  pTrigger.Id := Value;
end;

function TWinTaskTrigger.GetStartTime: TDateTime;
begin
  Result := BoundaryToDateTime(pTrigger.StartBoundary);
end;

procedure TWinTaskTrigger.SetStartTime(Value: TDateTime);
begin
  pTrigger.StartBoundary := DateTimeToBoundary(Value, FTimeZone);
end;

function TWinTaskTrigger.GetLogonUserId: string;
begin
  if FTriggerType = ttLogon then
    Result := ILogonTrigger(pTrigger).UserId
  else
    Result := '';
end;

procedure TWinTaskTrigger.SetLogonUserId(Value: string);
begin
  if FTriggerType = ttLogon then
    ILogonTrigger(pTrigger).UserId := Value;
end;

function TWinTaskTrigger.GetSubscription: string;
begin
  if FTriggerType = ttEvent then
    Result := IEventTrigger(pTrigger).Subscription
  else
    Result := '';
end;

procedure TWinTaskTrigger.SetSubscription(Value: string);
begin
  if FTriggerType = ttEvent then
    IEventTrigger(pTrigger).Subscription := Value;
end;

function TWinTaskTrigger.GetTriggerType: TWinTaskTriggerType;
begin
  case pTrigger.type_ of
    TASK_TRIGGER_EVENT:
      Result := ttEvent;
    TASK_TRIGGER_TIME:
      Result := ttTime;
    TASK_TRIGGER_DAILY:
      Result := ttDaily;
    TASK_TRIGGER_WEEKLY:
      Result := ttWeekly;
    TASK_TRIGGER_MONTHLY:
      Result := ttMonthly;
    TASK_TRIGGER_MONTHLYDOW:
      Result := ttMonthlyDow;
    TASK_TRIGGER_IDLE:
      Result := ttIdle;
    TASK_TRIGGER_REGISTRATION:
      Result := ttRegistration;
    TASK_TRIGGER_BOOT:
      Result := ttBoot;
    TASK_TRIGGER_LOGON:
      Result := ttLogon;
    TASK_TRIGGER_SESSION_STATE_CHANGE:
      Result := ttSessionStateChange;
  else
    Result := ttCustom; // TASK_TRIGGER_CUSTOM_TRIGGER_01
  end;
end;

function TWinTaskTrigger.GetTriggerTypeAsString: string;
begin
  Result := LoadResString(TaskTriggerDescriptions[TriggerType]);
end;

function TWinTaskTrigger.GetTriggerString: String;
var
  dt, dd: TDateTime;
  nr, nd: cardinal;

  function GetWeekdayList(Days: Integer): string;
  var
    i: Integer;
  begin
    Result := '';
    for i := 1 to 7 do
    begin
      if Days and 1 <> 0 then
      begin
        if length(Result) > 0 then
          Result := Result + ', ';
        Result := Result + FormatSettings.LongDayNames[i];
      end;
      Days := Days shr 1;
    end;
  end;

  function WeeksOfMonthList(Weeks: Integer): string;
  var
    i: Integer;
  begin
    Result := '';
    for i := 1 to 6 do
    begin
      if Weeks and 1 <> 0 then
      begin
        if length(Result) > 0 then
          Result := Result + ', ';
        Result := Result + LoadResString(TaskWeeksOfMonthList[i]);
      end;
      Weeks := Weeks shr 1;
    end;
  end;

  function GetMonthDayList(Days: Integer): string;
  var
    i: Integer;
  begin
    Result := '';
    for i := 1 to 32 do
    begin
      if Days and 1 <> 0 then
      begin
        if length(Result) > 0 then
          Result := Result + ', ';
        if i = 32 then
          Result := Result + rsTrgLast
        else
          Result := Result + IntToStr(i);
      end;
      Days := Days shr 1;
    end;
  end;

  function GetMonthList(Months: Integer): string;
  var
    i: Integer;
  begin
    Result := '';
    for i := 1 to 12 do
    begin
      if Months and 1 <> 0 then
      begin
        if length(Result) > 0 then
          Result := Result + ', ';
        Result := Result + FormatSettings.LongMonthNames[i];
      end;
      Months := Months shr 1;
    end;
  end;

  function FormatSecondsToTimeString(Seconds: cardinal): string;
  var
    h, m, s: word;
  begin
    if Seconds >= SecsPerDay then
      Result := GetUnitNumber(rsTrgEveryDay, rsTrgEveryDays,
        Seconds div SecsPerDay)
    else
    begin
      DivMod(Seconds, SecsPerHour, h, m);
      DivMod(m, SecsPerMin, m, s);
      if h > 0 then
        Result := GetUnitNumber(rsTrgEveryHour, rsTrgEveryHours, h)
      else if m > 0 then
        Result := GetUnitNumber(rsTrgEveryMin, rsTrgEveryMins, m)
      else
        Result := GetUnitNumber(rsTrgEverySec, rsTrgEverySecs, s);
    end;
  end;

  function TimeToStr(dt: TDateTime): string;
  // replacement for routine from sysutils
  begin
    Result := FormatDateTime('hh:nn', dt);
  end;

begin
  dt := BoundaryToDateTime(pTrigger.StartBoundary); // 2016-12-19T16:55:00
  dd := DateOf(dt);
  dt := TimeOf(dt);
  case pTrigger.type_ of
    // TASK_TRIGGER_EVENT:
    TASK_TRIGGER_TIME:
      Result := TryFormat(rsTrgOnce, [TimeToStr(dt), DateToStr(dd)]);
    TASK_TRIGGER_DAILY:
      with pTrigger as IDailyTrigger do
      begin
        if DaysInterval > 1 then
        begin
          Result := TryFormat(rsTrgRDailyAt, [TimeToStr(dt), DaysInterval]);
          if length(Repetition.Interval) > 0 then
          begin
            nr := TimeStringToSeconds(Repetition.Interval);
            nd := TimeStringToSeconds(Repetition.Duration);
            Result := Result + TryFormat(rsTrgRepeat,
              [FormatSecondsToTimeString(nr), FormatSecondsToTimeString(nd)]);
          end;
        end
        else
          Result := TryFormat(rsTrgDailyAt, [TimeToStr(dt)]);
      end;
    TASK_TRIGGER_WEEKLY:
      with pTrigger as IWeeklyTrigger do
      begin
        if WeeksInterval > 1 then
          Result := TryFormat(rsTrgRWeekly,
            [TimeToStr(dt), GetWeekdayList(DaysOfWeek), WeeksInterval,
            DateToStr(dd)])
        else
          Result := TryFormat(rsTrgWeekly,
            [TimeToStr(dt), GetWeekdayList(DaysOfWeek), DateToStr(dd)]);
        // Result:=TryFormat(,[])
      end;
    TASK_TRIGGER_MONTHLY:
      with pTrigger as IMonthlyTrigger do
      begin
        Result := TryFormat(rsTrgMonthly,
          [TimeToStr(dt), GetMonthDayList(DaysOfMonth),
          GetMonthList(MonthsOfYear), DateToStr(dd)])
      end;
    TASK_TRIGGER_MONTHLYDOW:
      with pTrigger as IMonthlyDowTrigger do
      begin
        Result := TryFormat(rsTrgDMonthly, [WeeksOfMonthList(WeeksOfMonth),
          GetWeekdayList(DaysOfWeek), GetMonthList(MonthsOfYear),
          DateToStr(dd)])
      end;
    TASK_TRIGGER_IDLE:
      Result := rsTrgIdle;
    TASK_TRIGGER_REGISTRATION:
      Result := rsTrgRegistration;
    TASK_TRIGGER_BOOT:
      begin
        Result := rsTrgStartup;
        if StartTime >= 1 then
          Result := Result + TryFormat(rsTrgEventTime,
            [TimeToStr(dt), DateToStr(dd)]);
      end;
    TASK_TRIGGER_LOGON:
      begin
        if length(LogonUserId) = 0 then
          Result := rsTrgAnyLogon
        else
          Result := TryFormat(rsTrgLogon, [LogonUserId]);
        if StartTime >= 1 then
          Result := Result + TryFormat(rsTrgEventTime,
            [TimeToStr(dt), DateToStr(dd)]);
      end;
    TASK_TRIGGER_EVENT:
      begin
        Result := rsTrgEvent;
        if StartTime >= 1 then
          Result := Result + TryFormat(rsTrgEventTime,
            [TimeToStr(dt), DateToStr(dd)]);
      end;
    // TASK_TRIGGER_SESSION_STATE_CHANGE = $0000000B;
    // TASK_TRIGGER_CUSTOM_: '+TRIGGER_01 = $0000000C;
  else
    Result := rsTrgUnknown;
  end;
end;

{ ------------------------------------------------------------------- }
constructor TWinTaskTriggers.Create(ATask: TWinTask);
begin
  inherited Create(TWinTaskTrigger);
  FTask := ATask;
end;

function TWinTaskTriggers.GetItem(Index: Integer): TWinTaskTrigger;
begin
  Result := TWinTaskTrigger(inherited GetItem(Index));
end;

procedure TWinTaskTriggers.SetItem(Index: Integer; Value: TWinTaskTrigger);
begin
  inherited SetItem(Index, Value);
end;

function TWinTaskTriggers.New(ATriggerType: TWinTaskTriggerType)
  : TWinTaskTrigger;
begin
  Result := TWinTaskTrigger(Add);
  Result.SetTaskTrigger(FTask.pDefinition.Triggers.Create
    (TriggerTypes[ATriggerType]));
end;

function TWinTaskTriggers.Add(ATrigger: ITrigger): TWinTaskTrigger;
begin
  Result := TWinTaskTrigger(Add);
  Result.SetTaskTrigger(ATrigger);
end;

procedure TWinTaskTriggers.ClearAll;
begin
  FTask.pDefinition.Triggers.Clear;
  Clear;
end;

procedure TWinTaskTriggers.Remove(AIndex: Integer);
begin
  FTask.pDefinition.Triggers.Remove(AIndex + 1);
  Delete(AIndex);
end;

{ ------------------------------------------------------------------- }
constructor TWinTask.Create(const ADefinition: ITaskDefinition);
begin
  inherited Create;
  pDefinition := ADefinition;
  FData := TMemoryStream.Create;
  FActions := TWinTaskActions.Create(Self);
  FTriggers := TWinTaskTriggers.Create(Self);
  FSelectedAction := -1;
end;

destructor TWinTask.Destroy;
begin
  FreeAndNil(FTriggers);
  FreeAndNil(FActions);
  FreeAndNil(FData);
  pDefinition := nil;
  inherited Destroy;
end;

function TWinTask.GetAuthor: string;
begin
  Result := pDefinition.RegistrationInfo.Author;
end;

procedure TWinTask.SetAuthor(Value: string);
begin
  pDefinition.RegistrationInfo.Author := Value;
end;

function TWinTask.GetData: string;
begin
  Result := pDefinition.Data;
end;

procedure TWinTask.SetData(Value: string);
begin
  pDefinition.Data := Value;
end;

function TWinTask.GetDate: TDateTime;
begin
  Result := BoundaryToDateTime(pDefinition.RegistrationInfo.Date);
end;

function TWinTask.GetDateAsString: string;
begin
  Result := DateTimeToText(GetDate);
end;

procedure TWinTask.SetDate(Value: TDateTime);
begin
  pDefinition.RegistrationInfo.Date := DateTimeToBoundary(Value, false);
end;

function TWinTask.GetDescription: string;
begin
  Result := pDefinition.RegistrationInfo.Description;
end;

procedure TWinTask.SetDescription(Value: string);
begin
  pDefinition.RegistrationInfo.Description := Value;
end;

function TWinTask.GetDocumentation: string;
begin
  Result := pDefinition.RegistrationInfo.Documentation;
end;

procedure TWinTask.SetDocumentation(Value: string);
begin
  pDefinition.RegistrationInfo.Documentation := Value;
end;

function TWinTask.GetDisplayName: string;
begin
  Result := pDefinition.Principal.DisplayName;
end;

procedure TWinTask.SetDisplayName(Value: string);
begin
  pDefinition.Principal.DisplayName := Value;
end;

function TWinTask.GetGroupId: string;
begin
  Result := pDefinition.Principal.GroupId;
end;

procedure TWinTask.SetGroupId(Value: string);
begin
  pDefinition.Principal.GroupId := Value;
end;

function TWinTask.GetId: string;
begin
  Result := pDefinition.Principal.Id;
end;

procedure TWinTask.SetId(Value: string);
begin
  pDefinition.Principal.Id := Value;
end;

function TWinTask.GetUserId: string;
begin
  Result := pDefinition.Principal.UserId;
end;

procedure TWinTask.SetUserId(Value: string);
begin
  pDefinition.Principal.UserId := Value;
end;

function TWinTask.GetLogonType: TLogonType;
begin
  case pDefinition.Principal.LogonType of
    TASK_LOGON_NONE:
      Result := ltNone;
    TASK_LOGON_PASSWORD:
      Result := ltPassword;
    TASK_LOGON_S4U:
      Result := ltS4U;
    TASK_LOGON_INTERACTIVE_TOKEN:
      Result := ltToken;
    TASK_LOGON_GROUP:
      Result := ltGroup;
    TASK_LOGON_SERVICE_ACCOUNT:
      Result := ltService;
    TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD:
      Result := ltTokenPassword;
  end;
end;

procedure TWinTask.SetLogonType(Value: TLogonType);
begin
  pDefinition.Principal.LogonType := TaskFlags[Value];
end;

function TWinTask.GetCompatibility: TWinTaskCompatibility;
begin
  case pDefinition.Settings.Compatibility of
    TASK_COMPATIBILITY_AT:
      Result := tcAT;
    TASK_COMPATIBILITY_V1:
      Result := tcXP;
    TASK_COMPATIBILITY_V2:
      Result := tcVista; // default value for new task
    TASK_COMPATIBILITY_V2_1:
      Result := tcWin7;
  else
    Result := tcWin10;
  end;
end;

procedure TWinTask.SetCompatibility(Value: TWinTaskCompatibility);
begin
  pDefinition.Settings.Compatibility := Compatibilities[Value];
end;

function TWinTask.GetCompatibilityAsString: string;
begin
  Result := LoadResString(CompatibilityNames[GetCompatibility]);
end;

function TWinTask.GetRunIfMissed: boolean;
begin
  Result := pDefinition.Settings.StartWhenAvailable;
end;

procedure TWinTask.SetRunIfMissed(Value: boolean);
begin
  pDefinition.Settings.StartWhenAvailable := Value;
end;

function TWinTask.GetAction(Index: Integer): TWinTaskAction;
begin
  Result := TWinTaskAction(FActions.Items[Index]);
end;

function TWinTask.GetActionCount: Integer;
begin
  Result := FActions.Count;
end;

function TWinTask.GetTrigger(Index: Integer): TWinTaskTrigger;
begin
  Result := TWinTaskTrigger(FTriggers.Items[Index]);
end;

function TWinTask.GetTriggerCount: Integer;
begin
  Result := FTriggers.Count;
end;

function TWinTask.NewAction(AActionType: TWinTaskActionType): TWinTaskAction;
begin
  Result := FActions.New(AActionType);
  FSelectedAction := Result.Index;
end;

function TWinTask.DeleteAction(AIndex: Integer): boolean;
begin
  Result := true;
  if (AIndex >= 0) and (AIndex < ActionCount) then
  begin
    try
      FActions.Remove(AIndex);
    except
      Result := false;
    end;
  end;
end;

function TWinTask.NewTrigger(ATriggerType: TWinTaskTriggerType)
  : TWinTaskTrigger;
begin
  Result := FTriggers.New(ATriggerType);
  Result.SetToDefault;
end;

function TWinTask.DeleteTrigger(AIndex: Integer): boolean;
begin
  Result := true;
  if (AIndex >= 0) and (AIndex < TriggerCount) then
  begin
    try
      FTriggers.Remove(AIndex);
    except
      Result := false;
    end;
  end;
end;

procedure TWinTask.Refresh;
var
  i: word;
begin
  FTriggers.Clear;
  FActions.Clear;
  with pDefinition.Triggers do
    for i := 1 to Count do
      FTriggers.Add(Item[i]);
  with pDefinition.Actions do
    for i := 1 to Count do
      FActions.Add(Item[i]);
end;

function TWinTask.IndexOfApp(const AppFilter: string): Integer;
begin
  for Result := 0 to ActionCount - 1 do
    if (Actions[Result].ActionType = taExec) and
      AnsiEndsText(AppFilter, TWinTaskExecAction(Actions[Result])
      .ApplicationPath) then
      Break;
  if Result >= ActionCount then
    Result := -1;
  FSelectedAction := Result;
end;

function TWinTask.IndexOfNextExecAction(FromIndex: Integer): Integer;
begin
  for Result := FromIndex to ActionCount - 1 do
    if Actions[Result].ActionType = taExec then
      Break;
  if Result >= ActionCount then
    Result := -1;
  FSelectedAction := Result;
end;

{ ------------------------------------------------------------------- }
constructor TWinRegisteredTask.Create(const ARegisteredTask: IRegisteredTask);
begin
  inherited Create;
  pRegisteredTask := ARegisteredTask;
  FIndex := -1;
  FTaskName := pRegisteredTask.Name;
  FTask := TWinTask.Create(pRegisteredTask.Definition);
  FTask.Refresh;
end;

destructor TWinRegisteredTask.Destroy;
begin
  pRegisteredTask := nil;
  inherited Destroy;
end;

procedure TWinRegisteredTask.Refresh;
begin
  FTask.Refresh;
end;

procedure TWinRegisteredTask.Run;
begin
  pRegisteredTask.Run(VT_EMPTY)
end;

procedure TWinRegisteredTask.Stop;
begin
  pRegisteredTask.Stop(0);
end;

function TWinRegisteredTask.GetTaskResult: Integer;
begin
  Result := pRegisteredTask.LastTaskResult;
end;

function TWinRegisteredTask.GetLastRunTime: TDateTime;
begin
  Result := pRegisteredTask.LastRunTime;
end;

function TWinRegisteredTask.GetLastRunTimeAsString: string;
begin
  Result := DateTimeToText(LastRunTime);
end;

function TWinRegisteredTask.GetNextRunTime: TDateTime;
begin
  Result := pRegisteredTask.NextRunTime;
end;

function TWinRegisteredTask.GetNextRunTimeAsString: string;
begin
  Result := DateTimeToText(NextRunTime);
end;

function TWinRegisteredTask.GetNumberOfMissedRuns: Integer;
begin
  Result := pRegisteredTask.NumberOfMissedRuns;
end;

function TWinRegisteredTask.GetPath: string;
begin
  Result := AnsiDequotedStr(pRegisteredTask.Path, '"');
end;

function TWinRegisteredTask.GetEnabled: boolean;
begin
  Result := pRegisteredTask.Enabled;
end;

procedure TWinRegisteredTask.SetEnabled(Value: boolean);
begin
  pRegisteredTask.Enabled := Value;
end;

function TWinRegisteredTask.GetStatus: TWinTaskStatus;
begin
  case pRegisteredTask.State of
    TASK_STATE_DISABLED:
      Result := tsDisabled;
    TASK_STATE_QUEUED:
      Result := tsQueued;
    TASK_STATE_READY:
      Result := tsReady;
    TASK_STATE_RUNNING:
      Result := tsRunning;
  else
    Result := tsUnknown;
  end;
end;

function TWinRegisteredTask.GetStatusAsString: string;
begin
  Result := LoadResString(TaskStateNames[Status]);
end;

function TWinRegisteredTask.IndexOfApp(const AppFilter: string): Integer;
begin
  Result := FTask.IndexOfApp(AppFilter);
end;

{ ------------------------------------------------------------------- }
// function TWinTaskList.Add (ARegTask : TWinRegisteredTask) : integer;
// begin
// Result:=inherited Add(ARegTask);
// ARegTask.TaskIndex:=Result;
// end;

{ ------------------------------------------------------------------- }
constructor TWinTaskFolder.Create(ARootFolder: ITaskFolder);
begin
  inherited Create;
  FTasks := TWinTaskList.Create;
  pRootFolder := ARootFolder;
  FErrMsg := '';
  FErrorCode := ERROR_SUCCESS;
end;

destructor TWinTaskFolder.Destroy;
begin
  FreeAndNil(FTasks);
  pTaskCollection := nil;
  pRootFolder := nil;
  inherited Destroy;
end;

function TWinTaskFolder.GetTask(Index: Integer): TWinRegisteredTask;
begin
  Result := TWinRegisteredTask(FTasks.Items[Index]);
end;

function TWinTaskFolder.Refresh: HResult;
var
  i: Integer;
  pRegisteredTask: IRegisteredTask;
  ARegTask: TWinRegisteredTask;
begin
  try
    // Get the registered tasks in the folder.
    pTaskCollection := pRootFolder.GetTasks(0); // TASK_ENUM_HIDDEN
    FNumTasks := pTaskCollection.Count;
    FTasks.Clear;
    for i := 1 to FNumTasks do
    begin
      ARegTask := TWinRegisteredTask.Create(pTaskCollection.Item[i]);
      ARegTask.Refresh;
      ARegTask.TaskIndex := FTasks.Add(ARegTask);
    end;
    Result := ERROR_SUCCESS;
  except
    on E: EOleSysError do
      Result := E.ErrorCode;
  end;
end;

function TWinTaskFolder.IndexOf(const TaskName: string): Integer;
begin
  for Result := 0 to TaskCount - 1 do
    if AnsiSameText(Tasks[Result].TaskName, TaskName) then
      Exit;
  Result := -1;
end;

function TWinTaskFolder.DeleteTask(const TaskName: string): cardinal;
begin
  Result := ERROR_SUCCESS;
  FErrMsg := '';
  FErrorCode := ERROR_SUCCESS;
  try
    pRootFolder.DeleteTask(TaskName, 0);
    Refresh;
  except
    on E: EOleSysError do
      with E do
      begin
        FErrMsg := Message;
        FErrorCode := ErrorCode;
        Result := ErrorCode;
      end;
  end;
end;

// Result >=0: Number of new task
// <0: Error
function TWinTaskFolder.RegisterTask(const TaskName: string; ATask: TWinTask;
  Username, Password: string): Integer;
var
  pRegisteredTask: IRegisteredTask;
  ARegTask: TWinRegisteredTask;
  n: Integer;
begin
  Result := -1;
  FErrMsg := '';
  FErrorCode := NO_ERROR;
  if ATask.LogonType = ltToken then
  begin
    Username := '';
    Password := '';
  end;
  try
    pRegisteredTask := pRootFolder.RegisterTaskDefinition(TaskName,
      ATask.pDefinition, TASK_CREATE_OR_UPDATE, Username, Password,
      TaskFlags[ATask.LogonType], '');
    Refresh;
    Result := IndexOf(TaskName);
    // if n>=0 then begin
    // ARegTask:=Tasks[n];
    // ARegTask.Refresh;
    // Result:=n;
    // end
    // else begin
    // ARegTask:=TWinRegisteredTask.Create(pRegisteredTask);
    // ARegTask.Refresh;
    // ARegTask.TaskIndex:=FTasks.Add(ARegTask);
    // Result:=ARegTask.TaskIndex;
    // end;
  except
    on E: EOleSysError do
      with E do
      begin
        FErrMsg := Message;
        FErrorCode := ErrorCode;
        Result := ErrorCode;
      end;
  end;
end;

{ ------------------------------------------------------------------- }
constructor TWinTaskScheduler.Create;
begin
  inherited Create;
  // FResult:=CoInitialize(nil); //Ex(nil,COINIT_MULTITHREADED);
  // Create an instance of the Task Service.
  // CoCreateInstance(CLSID_TaskScheduler,nil,CLSCTX_INPROC_SERVER,IID_ITaskService,pService);
  FResult := CoInitializeSecurity(nil, -1, nil, nil, RPC_C_AUTHN_LEVEL_PKT,
    RPC_C_IMP_LEVEL_IMPERSONATE, nil, 0, nil);
  if succeeded(FResult) or (FResult = RPC_E_TOO_LATE) then
  begin
    try
      // Create an instance of the Task Service.
      pService := CoTaskScheduler_.Create;
      // pService:=CreateComObject(CLSID_TaskScheduler) as ITaskService;
      FResult := ERROR_SUCCESS;
    except
      on E: EOleSysError do
        FResult := E.ErrorCode;
    end;
  end;
  FFolder := '\'; // set to root
end;

destructor TWinTaskScheduler.Destroy;
begin
  FreeAndNil(FTaskFolder);
  // pService:=nil;
  // CoUninitialize;
  inherited Destroy;
end;

{ $O- }     // does not work with optimization ??
function TWinTaskScheduler.Init: HResult;
var
  ok: boolean;
begin
  if succeeded(FResult) then
  begin
    // Connect to the local task service.
    Result := pService.Connect(Null, Null, Null, Null);
    // Null instead of '' required for Vista
    if succeeded(Result) then
      Result := Refresh;
  end
  else
    Result := FResult;
end;

{$O+}

class function TWinTaskScheduler.IsRunning: boolean;
begin
  Result := GetServiceStatusByName('', 'schedule') = ssRunning;
end;

procedure TWinTaskScheduler.SetFolder(const AFolder: string);
begin
  FFolder := AFolder;
  Refresh;
end;

function TWinTaskScheduler.Refresh: HResult;
begin
  try
    // Get the pointer to the root task folder.
    if assigned(FTaskFolder) then
      FTaskFolder.Free;
    FTaskFolder := TWinTaskFolder.Create(pService.GetFolder(FFolder));
    Result := FTaskFolder.Refresh;
  except
    on E: EOleSysError do
      Result := E.ErrorCode;
  end;
end;

function TWinTaskScheduler.NewTask: TWinTask;
begin
  Result := TWinTask.Create(pService.NewTask(0));
  Result.Compatibility := GetDefaultCompatibility;
  // appropriate to current system
end;


// function TWinTaskScheduler.Add (const TaskName: String) : TWinTask;
// var
// pRegisteredTask : IRegisteredTask;
// n     : integer;
// begin
// pRegisteredTask:=pService.NewTask(0);
// Result:=TWinTask.Create(pRegisteredTask.Name,pRegisteredTask);
// Result.Refresh;
// n:=FTasks.Add(Result);
// Result.TaskIndex:=n;
// end;
//

function CreateWinTaskScheduler(var TaskSchedule: TWinTaskScheduler): HResult;
begin
  if Win32MajorVersion < 6 then
    Result := NotAvailOnXp // WindowsXP or older
  else if TWinTaskScheduler.IsRunning then
  begin
    Result := CoInitializeSecurity(nil, -1, nil, nil, RPC_C_AUTHN_LEVEL_PKT,
      RPC_C_IMP_LEVEL_IMPERSONATE, nil, 0, nil);
    if succeeded(Result) or (Result = RPC_E_TOO_LATE) then
    begin
      TaskSchedule := TWinTaskScheduler.Create;
      Result := TaskSchedule.Init;
    end;
  end
  else
    Result := SCHED_E_SERVICE_NOT_RUNNING;
end;

end.
