PascalScript in Syncovery

Syncovery includes a PascalScript engine, allowing you to customize your profile’s behavior in many ways. PascalScript was added in Syncovery 8, and many new hooks and functions have been added since the first release.

To change a particular behavior, you need to write a hook function and write some code. In most cases, our tech support will write the code for you. For larger or complicated scripts, please consider a Premium Support Subscription that includes custom script development.

The script is specified in the profile via the “Pascal Script…” checkmark on the Job settings category tab sheet.

Since version 9.36b, PascalScripts have access to all profile settings and can read and modify them using the utility functions GetProfileProperty and SetProfileProperty.

Useful ready-to-use scripts

The following scripts can be used as it is, just paste the script into the PascalScript dialog.

  • Shorten long file names
    This script shortens file names which are over 114 characters long, adding a CRC32 hash based on the original file name to ensure that the shortened names are unique.
  • Convert Disallowed Characters for Windows
    This script allows you do have the disallowed characters /:\?|<>“* in file names on the left-hand side. The right-hand side can be Windows compliant storage such as a local NTFS drive.
  • Simple E-Mail Notifications
    This script simplifies the e-mail notifications and can be edited easily to customize your emails.
  • Send E-Mail Notifications Depending on Profile Results
    This script causes e-mail notifications to be sent only if less than 10 or more than 100 files were copied from left to right.

Available PascalScript Hooks

The following hooks are currently available. Additional hooks will be added as needed to fulfill customer requests.

  • OnActionComplete
  • OnCanRunProfile
  • OnDownloadComplete
  • OnFileCopy
  • OnGetCustomHeaders
  • OnGetNextRunTime
  • OnGetProfilePathBeforeListing
  • OnGetProfilePathBeforeCopying
  • OnHttpPost
  • OnIncludeItem
  • OnProfileResults
  • OnMoveFileToDeletedFolder
  • OnNormalizeFilename
  • OnReplaceFilenameLeftToRight
  • OnReplaceFilenameRightToLeft
  • OnScanFolder
  • OnSendEmail
  • OnUploadComplete
  • OnVolumeShadowComplete
  • OnBeforeFolderCreate
  • OnBeforeFileCopy
  • OnBeforeMainActionPhase

Available PascalScript Functions

The following functions are available for you to call.

  • standard functions such as Pos, Copy, Length, Ord
     
  • function GetProfileProperty(const fieldname:UnicodeString):UnicodeString;
  • function SetProfileProperty(const fieldname:UnicodeString;const val:UnicodeString):Boolean;
    (these functions use the same field names as the XML format and the command line. See the settings dictionary at the end of the Syncovery Command Line page)
  • function SaveProfileSettings:Boolean;
  • function ConcatPath(const a,b:UnicodeString; const t: Int64):UnicodeString;
  • function ConcatPathWithDelim(const a,b,delim:UnicodeString):UnicodeString;
  • function ExtractFileName(const s: UnicodeString):UnicodeString;
  • function ExtractFilePath(const s: UnicodeString):UnicodeString;
  • function ExtractURLPartAfterServer(const s: UnicodeString):UnicodeString;
  • function ExtractFileExt(const s: UnicodeString):UnicodeString;
  • function ChangeFileExt(const s,t: UnicodeString):UnicodeString;
  • function FileExists(const FileName: UnicodeString):Boolean;
  • function FileExistsMatching(const FileName: UnicodeString):Boolean;
  • (can be used with wildcards in the file name, but not in the parent folders)

  • function EntryExists(const FileName: UnicodeString):Boolean;
  • function FileAge(const FileName: UnicodeString):Double;
  • function FileCopy(const ASource,ADest:UnicodeString):Int64;
     
  • function ProfileRunning(const s: UnicodeString):Boolean;
  • procedure Log(const s:UnicodeString);
  • procedure MessageBox(const s:UnicodeString);
  • function Execute(const s:UnicodeString; const TimeOutSeconds: Int64):Int64;
  • function GetProfileName:UnicodeString;
  • procedure SetProfileResult(const AResultText:UnicodeString);
     
  • function CreateS3Connector(const BucketName,AccessID,SecretKey:UnicodeString;const Options:Integer):Opaque;
  • function UploadFile(const LocalPath,DestinationPath:UnicodeString;const Connector:Opaque):Int64;
  • function CloseConnector(const Connector:Opaque):Int64;
  • function ConnFileExists(const Connector:Opaque; const FileName: UnicodeString):Boolean;
  • function ConnDirectoryExists(const Connector:Opaque; const Name: UnicodeString):Boolean;
  • function ConnCustomFTPCommand(const Connector:Opaque; const ACommand: UnicodeString;
         const AOkResponse1,AOkResponse2,AOkResponse3:Integer;
         var ResponseText: UnicodeString):Integer;
  • function ConnProcessWebForm(const Connector:Opaque;
         const URL, AFormName, AField1, AValue1, AField2, AValue2: AnsiString;
         const SaveResultPage: Boolean;
         const SavePageFileName: UnicodeString): Boolean;
  • function ConnRenameFile(const Connector:Opaque; const AFromFileName,AToFileName: UnicodeString):Boolean;
  • function ConnDeleteFile(const Connector:Opaque; const AFileName: UnicodeString):Boolean;
  • function ConnDeleteFiles(const Connector:Opaque; const APath,AMask: UnicodeString):Integer;
  • function ConnDeleteFilesOlderThan(const Connector:Opaque; const APath,AMask: UnicodeString;const AWhen:Double):Integer;
     
  • function SendHTTPRequest(const requestype,mimetype,additionalheaders,URL,PostData:AnsiString;var ResultCode:Int64;var Response,ErrorResponse:AnsiString):Boolean;
     
  • function StringReplace(const Source, OldPattern, NewPattern: UnicodeString;const CaseSensitive:Boolean): UnicodeString;
  • function EncodeBase64(const s: UnicodeString):UnicodeString;
  • function DecodeBase64(const s: UnicodeString):UnicodeString;
  • function Utf8Encode(const s: UnicodeString):AnsiString;
  • function Utf8Decode(const s: AnsiString):UnicodeString;
  • function UnicodeStringMD5Hex(const s: UnicodeString):UnicodeString;
  • function EightBitStringMD5Hex(const s: AnsiString):AnsiString;
  • function UnicodeStringMD5Base64(const s: UnicodeString):UnicodeString;
  • function EightBitStringMD5Base64(const s: AnsiString):AnsiString;
  • function UnicodeStringCRC32Hex(const s: UnicodeString):UnicodeString;
  • function EightBitStringCRC32Hex(const s: AnsiString):AnsiString;
  • function UnicodeStringCRC32Base64(const s: UnicodeString):UnicodeString;
  • function EightBitStringCRC32Base64(const s: AnsiString):AnsiString;
  • function SimpleEncrypt(const s: UnicodeString):UnicodeString;
  • function SimpleDecrypt(const s: UnicodeString):UnicodeString;
  • function AESEncrypt(const s,passphrase: UnicodeString):UnicodeString;
  • function AESDecrypt(const s,passphrase: UnicodeString):UnicodeString;
     
  • function ChooseFile(const Prompt,Extension:UnicodeString):UnicodeString;
  • function OpenIniFile(const AFileName:UnicodeString):Int64;
  • procedure CloseIniFile(const AnIni:Int64);
  • function ReadIniString(const AnIni:Int64;const Section,Ident,Default:UnicodeString):UnicodeString;
  • function GetInput(const s: UnicodeString):UnicodeString;
  • function GetPassword(const s: UnicodeString):UnicodeString;
  • function ReadRegistryString(const Key,OptName:UnicodeString):UnicodeString;
  • procedure WriteRegistryString(const Key,OptName,Value:UnicodeString);
  • function GetProfileRunID:UnicodeString;
     
  • procedure ClearBody;
  • function GetBodyLine(const i:Integer):UnicodeString;
  • procedure SetBodyLine(const i:Integer;const s:UnicodeString);
  • procedure DeleteBodyLine(const i:Integer);
  • procedure AddBodyText(const s:UnicodeString);
  • function GetActionList:UnicodeString;
  • function GetProfileSettings:UnicodeString;
     
  • function Now:Double;
  • function NowUTC:Double;
  • function OffsetFromUTC:Double;
  • function TimeToStr(const DateTime: Double):UnicodeString;
  • function DateTimeToStr(const DateTime: Double):UnicodeString;
  • function DateToStr(const DateTime: Double):UnicodeString’;
  • function DateTimeToStrWithFormat(const DateTime: Double;const FormatString:UnicodeString):UnicodeString;
     
  • function MakeSurePathExists(const s:UnicodeString;const isRightSide:Boolean):Boolean;
  • function PathDelim:UnicodeString;
  • function GetDelim(const Connector: Opaque):UnicodeString;
  • function IncludeLeadingPathDelim(const s: UnicodeString):UnicodeString;
     
  • function OpenTextFile(const s:UnicodeString):Opaque;
  • function AppendTextFile(const s:UnicodeString):Opaque;
  • function CreateTextFile(const s:UnicodeString):Opaque;
  • procedure WriteLine(const F:Opaque;const ALine:UnicodeString);
  • function ReadLine(const F:Opaque):UnicodeString;
  • procedure CloseFile(const F:Opaque);
  • function AtomicAppendTextFileLine(const APath,ALine:UnicodeString):Boolean;
    (appends a line in a multi-thread and multi-process safe way)
     
  • function YearOf(const dt:Double):Integer;
  • function MonthOf(const dt:Double):Integer;
  • function WeekOf(const dt:Double):Integer;
  • function DayOf(const dt:Double):Integer;
  • function DayOfTheWeek(const dt:Double):Integer;
  • function SecondOf(const dt:Double):Integer;
  • function HourOf(const dt:Double):Integer;
  • function MinuteOf(const dt:Double):Integer;
  • function EncodeDateTime(const AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond: Integer): Double;

Available Global Variables

These global variables allow direct access to Syncovery’s internal fields. Some of them should be considered read-only while others are meant to be changed as needed.

  • ProfileTempDir: UnicodeString;
  • LeftBasePath: UnicodeString;
  • RightBasePath: UnicodeString;
  • ProfileName: UnicodeString;
  • ResultSummary: UnicodeString;
  • MinimumFreeSpaceLeft: Int64;
  • MinimumFreeSpaceRight: Int64;
  • MaxWaitWhenFreeSpaceLowSeconds: Integer;
  • MinDate: Double;
  • MaxDate: Double;
  • FilterTimestamps: Boolean;
  • UseBinaryCompToAvoidCopying: Boolean;
  • FileCopyNoStatNeeded: Boolean;

OnActionComplete Sample Script

This hook is called after any type of file or folder related action has been carried out, such as a file copied, moved, or deleted and so forth. The example just logs the action. The function result is ignored by Syncovery.

function OnActionComplete(const StartTimeUTC, CompletionTimeUTC: Double;
        const Success: Boolean;
        const ActionStr, DirectionStr, Filename,
              LeftFile, RightFile, Subfolder, MovedTo,
              ErrorMsg: UnicodeString;
        const ResultCode: Int64;
        const ErrorSide: UnicodeString;
        const ASize,ACompressedSize:Int64):Boolean;
begin
  Log('Action Complete: '+ActionStr+' '+DirectionStr+' '+Filename);
  Result:=true;
  end;

OnCanRunProfile Sample Script

This hook is called when the scheduler is about to run a profile. You can decide if it should actually run now, or be postponed. The example uses the hook to prevent two particular profiles from running at the same time, or overlapping. This script must be entered as a Global PascalScript via the Program Settings dialog, tab sheet Advanced.

const p1='Profile Name 1';
      p2='Profile Name 2';

function OnCanRunProfile(const ProfileName:UnicodeString; var PostponeBySeconds:Integer):Boolean;
begin
  if ProfileName=p1 then
     Result:=not ProfileRunning(p2)
  else
     if ProfileName=p2 then
       Result:=not ProfileRunning(p1)
     else
       Result:=true;
  if not Result then
     PostponeBySeconds:=60;
  end;

OnFileCopy Sample Script

This hook allows you to replace the file copying logic. Return 0 on success, -1 if Syncovery should do the copying a usual, or any other value as an error code.

function OnFileCopy(const DirectionIsL2R:Boolean;
        const Source,Dest,DestPath,LeftSubPath,RightSubPath:UnicodeString;
        const SourceConnector,DestConnector: Opaque):Int64;
begin
  Result:=Execute('XCOPY.EXE "'+Source+'" "'+DestPath+'"',60);
  end;

OnGetCustomHeaders Sample Script

This hook allows you to add custom HTTP headers to Amazon S3 requests. In future Syncovery versions, this feature can be added to other cloud storages and protocols (on customer request – just ask for it).

function OnGetCustomHeaders(const RelativePath: UnicodeString;
        const URL:AnsiString;var MIMEType, Headers: AnsiString;
        const Connector:Opaque):Boolean;
var strDate:string;
begin
  Result:=true;
  Headers:='Cache-Control: 10';
  Log('');
  Log('Headers Added to '+RelativePath);
  Log(Headers);
  Log('');
  end;

OnGetNextRunTime Sample Script

This hook allows you to modify the scheduling. The best way to use it is to give the profile a regular, simple schedule, and use the hook to skip undesired run times. This example is intended for a profile that is scheduled to run “every day at XX:YY”. The hook ensures that in fact it runs only on the first weekday in a month.

function OnGetNextRunTime(const LastRun, ProposedNextRun: Double):Double;
var DidItRunThisMonth:Boolean;
begin
  Result:=ProposedNextRun;
 
  DidItRunThisMonth:=(MonthOf(LastRun)=MonthOf(Now)) and
      (MonthOf(LastRun)=MonthOf(ProposedNextRun)) and
      (DayOf(LastRun)<DayOf(ProposedNextRun));
 
  if DidItRunThisMonth then begin
    // go to the next month
    while DayOf(Result)>1 do
      Result:=Result+1;
    end
  else begin
    // go to the next month if we are beyond week #1
    while DayOf(Result)>7 do
      Result:=Result+1;
    end;
 
  // second, advance further until it's not a weekend
  while DayOfTheWeek(Result)>=6 do
    Result:=Result+1;
  end;

OnIncludeItem Sample Scripts

The OnIncludeItem hook is called individually for each side of the synchronization. The isRightSide parameter indicates which side we are currently looking at.

The following script serves to exclude files without filename extension. Such an exclusion is not possible via the Exclusion Masks.

function OnIncludeItem(const FileName, RelativePath: UnicodeString;
        const isRightSide, isFolder:Boolean;
        const FileSize:Int64; const FileAttr:LongWord;
        const Connector: Opaque):Boolean;
begin
  Result:=isFolder or (Pos('.',FileName)>0);
  end;

The second example for OnIncludeItem will only process subfolders that contain the file READY.toprocess, as well as any subfolders that exist on the right-hand side.

function OnIncludeItem(const FileName, RelativePath: UnicodeString;
        const isRightSide, isFolder:Boolean;
        const FileSize:Int64; const FileAttr:LongWord;
        const Connector: Opaque):Boolean;
begin
  Result:=isRightSide or not isFolder or
    ConnFileExists(Connector,ConcatPath(ConcatPath(LeftBasePath,RelativePath,Connector),FileName,Connector)+
    '\READY.toprocess');
  end;

OnScanFolder Sample Script

This is probably a better way to look for ‘READY.toprocess’. The OnScanFolder hook is called just before the folder would be scanned, and knowledge of both sides can be used in the hook.

function OnScanFolder(const FolderLevel: Integer;
        const RelativePath, LeftCompletePath, RightCompletePath: UnicodeString;
        const LeftExists,RightExists:Boolean;
        const LeftConnector, RightConnector: Opaque):Boolean;
begin
  Result:=RightExists or ConnFileExists(LeftConnector,LeftCompletePath+'\READY.toprocess');
  end;

OnProfileResults Sample Script

This script does nothing, but the function is called with some statistics when a profile completes. An example with some real use can be downloaded near the top of this page (“Send E-Mail Notifications Depending on Profile Results”). This functions return value (“Result”) is ignored.

function OnProfileResults(const FilesCopiedL2R,FilesCopiedR2L:Integer;
        const FilesToCopyL2R,FilesToCopyR2L:Integer;
        const BytesCopiedL2R,BytesCopiedR2L:Int64;
        const ResultString:UnicodeString;
        const Error:Boolean):Boolean;
begin
  Result:=true;
  end;

OnNormalizeFilename Sample Script

This hook allows you to “normalize” filenames so that Syncovery can see file names as identical despite minor differences. The sample script normalizes space characters, so that there will always be only one space character between other characters. Two or more consecutive spaces are normalized to just one space. The result is that Syncovery sees files as identical which have different numbers of consecutive spaces on each side of the profile. These filenames are not modified on disk. The modification is purely in memory for comparison purposes. The Sync Preview will show the normalized file names.

function OnNormalizeFilename(const FileName: UnicodeString;
        const isFolder, isRightSide: Boolean):UnicodeString;
var Changed:UnicodeString;
begin
  Result:=FileName;
  repeat
    Changed:=StringReplace(Result,' ',' ',true);
    if Changed=Result then
      break;
    Result:=Changed;
    until false;
  end;

OnReplaceFilenameLeftToRight Sample Script

This script will rename files when the file is copied from left to right. In our example, ‘-draft’ is added before the filename extension.

function OnReplaceFilenameLeftToRight(const FileName: UnicodeString;
        const isFolder: Boolean):UnicodeString;
begin
  if isFolder then
    Result:=FileName
  else
    Result:=ChangeFileExt(FileName,'-draft')+ExtractFileExt(FileName);
  end;

OnReplaceFilenameRightToLeft Sample Script

If you do a two-way sync, and new files might appear on the right-hand side, we also need a way to rename in the other direction. This sample script removes the ‘-draft’ insert from the names.

function OnReplaceFilenameRightToLeft(const FileName: UnicodeString;
        const isFolder: Boolean):UnicodeString;
var ToFind:UnicodeString;
    P:Integer;
begin
  if isFolder then
    Result:=FileName
  else begin
    ToFind:=''-draft''+ExtractFileExt(FileName);
    p:=Pos(ToFind,FileName);
    if p>0 then
      Result:=Copy(FileName,1,p-1)+ExtractFileExt(FileName)
    else
      Result:=FileName
    end;
  end;

OnUploadComplete Sample Script

This script will set permissions for files uploaded via FTP to 777.

function OnUploadComplete(const FileName, LocalFilePath, CompleteURL: UnicodeString;
        const isRightSide:Boolean;
        const FileSize:Int64;
        const Connector: Opaque):Boolean;
var CmdRes:Int64;
    ResponseText:UnicodeString;
begin
  CmdRes:=ConnCustomFTPCommand(Connector,'SITE CHMOD 777 '+FileName,200,200,200,ResponseText);
  Log('Set Permissions for '+FileName+': '+ResponseText);
  Result:=true;
  end;

OnDownloadComplete Sample Script

This script will rename downloaded files by adding an additional “.downloaded” extension.

function OnDownloadComplete(const FileName, CompleteURL, LocalFilePath: UnicodeString;
        const isRightSide:Boolean;
        const FileSize:Int64;
        const Connector: Opaque):Boolean;
begin
  if ConnRenameFile(Connector,CompleteURL,FileName+'.downloaded') then
    Log('Renamed '+FileName+' to '+FileName+'.downloaded')
  else
    Log('Rename failed: '+FileName+' to '+FileName+'.downloaded');
  Result:=true;
  end;

OnVolumeShadowComplete Sample Script

This script will execute an external command (batch or CMD file) just after a volume shadow has been created.

function OnVolumeShadowComplete(const Volume,ShadowPath:UnicodeString):Boolean;
begin
  Result:=Execute('C:\Tests\test.bat',120)=0;
  end;

OnBeforeFolderCreate Sample Script

This script will prevent any folder creation except where necessary to copy files to the destination. You can also use this hook to manipulate folder paths on the destination.

function OnBeforeFolderCreate(const DirectionIsL2R:Boolean;
        var Source,Dest,Reason:UnicodeString):Boolean;
begin
  Result:=false;
  end;

OnBeforeFileCopySample Script

This script will manipulate the destination paths for copying (left to right), by adding an additional subfolder ‘archive’ which is not present on the source side.

function OnBeforeFileCopy(const DirectionIsL2R:Boolean;
        var Source,Dest,DestPath,LeftSubPath,RightSubPath:UnicodeString):Boolean;
var AddFolderName,NewDestPath,NewDest,NewRightSubPath:UnicodeString;
begin
  Result:=true;
  if not DirectionIsL2R then
    Exit;
  AddFolderName:='archive';
  NewDestPath:=ConcatPathWithDelim(DestPath,AddFolderName,PathDelim);
  NewDest:=ConcatPathWithDelim(NewDestPath,ExtractFileName(Dest),PathDelim);
  NewRightSubPath:=IncludeLeadingPathDelim(ConcatPathWithDelim(RightSubPath,AddFolderName,PathDelim));
 
  if not MakeSurePathExists(NewDestPath,true) then begin
    Log('Could not create '+NewDestPath);
    Exit;
    end;
 
  Log('OnBeforeFileCopy');
  Log('Source:'+Source);
  Log('DestPath:'+DestPath+' changed to '+NewDestPath);
  Log('Dest:'+Dest+' changed to '+NewDest);
  Log('LeftSubPath:'+LeftSubPath);
  Log('RightSubPath:'+RightSubPath+' changed to '+NewRightSubPath);
  Log('');
 
  Dest:=NewDest;
  DestPath:=NewDestPath;
  RightSubPath:=NewRightSubPath;
  end;

OnGetProfilePathBeforeListing Sample Script

This script uses this hook to specify an individual TEMP folder for the profile. The original purpose of the function is to modify the left or right path and/or the credentials.

function OnGetProfilePathBeforeListing(const isRightSide:Boolean;
        var Path,UserName,Password:UnicodeString;
        var AuthKey,AuthKeyPassword:AnsiString;
        var Port:Integer):Boolean;
begin
  ProfileTempDir:='H:\TEMP';
  Result:=true;
  end;

OnBeforeMainActionPhase Sample Script

This script lets the job run only if a fixed number of files is to be copied from left to right.

function OnBeforeMainActionPhase(const CopyL2RCount,CopyR2LCount,DeleteLCount,DeleteRCount,MoveLCount,MoveRCount:Int64;
                const CopyL2RBytes,CopyR2LBytes,DeleteLBytes,DeleteRBytes,MoveLBytes,MoveRBytes:Int64):Boolean;
begin
  if CopyL2RCount=14 then
     Result:=true
  else begin
     Result:=false;
     Log('Not continuing because CopyL2RCount is '+IntToStr(CopyL2RCount));
     end;
  end;