Pascal Script: Examples
TODO: Review, tests all scripts.
This page provides practical examples demonstrating how to use the Pascal Script rule in ReNamer. Each example is self-contained and focuses on a specific task or concept. For function signatures and type details, refer to the Functions and Types reference articles.
Modifying FileName
The FileName variable is a WideString representing the new name for the current file, including the extension. Modifying it is the fundamental operation of any script. Assigning an empty string or raising an error causes the file to be skipped during renaming.
To add a prefix to every filename:
begin
FileName := 'MyPrefix_' + FileName;
end.
To add a suffix before the extension, split the base name and extension apart first:
var
BaseName, Ext: WideString;
begin
BaseName := WideExtractBaseName(FileName);
Ext := WideExtractFileExt(FileName);
FileName := BaseName + '_backup' + Ext;
end.
WideExtractBaseName returns the filename without path or extension. WideExtractFileExt returns the extension including the leading dot (e.g. .txt). This split-and-rebuild pattern appears throughout many scripts whenever the extension must be preserved while transforming only the base name.
Accessing FilePath and path components
The FilePath constant holds the original full path to the current file (e.g. C:\Music\Artist\Album\Track.flac). It is read-only, but is invaluable for reading file content, extracting metadata, and deriving folder-based names.
To prefix a filename with its immediate parent folder name:
begin
FileName := WideExtractFileName(WideExtractFileDir(FilePath)) + ' - ' + FileName;
end.
WideExtractFileDir returns the directory without a trailing backslash, and WideExtractFileName extracts just the last component — the immediate parent folder name.
For a deeper hierarchy, use WideSplitString to break the path into individual folder components:
var
Parts: TWideStringArray;
begin
Parts := WideSplitString(WideExtractFileDir(FilePath), '\');
// Parts[Length(Parts) - 1] is the immediate parent folder
// Parts[Length(Parts) - 2] is one level above, and so on
if Length(Parts) >= 2 then
FileName := Parts[Length(Parts) - 2] + ' - ' + Parts[Length(Parts) - 1] + ' - ' + FileName;
end.
Note: TWideStringArray is 0-based, so the last element is at index Length(Parts) - 1.
Case conversion
ReNamer provides several functions for Unicode-aware case conversion, all accepting and returning WideString.
To convert the entire filename to uppercase:
begin
FileName := WideUpperCase(FileName);
end.
To convert only the base name while preserving the extension:
var
BaseName, Ext: WideString;
begin
BaseName := WideExtractBaseName(FileName);
Ext := WideExtractFileExt(FileName);
FileName := WideUpperCase(BaseName) + Ext;
end.
The same pattern applies to all case functions. Given the input "the quick BROWN fox":
| Function | Result |
|---|---|
WideUpperCase |
THE QUICK BROWN FOX |
WideLowerCase |
the quick brown fox |
WideCaseCapitalize |
The Quick Brown Fox |
WideCaseSentence |
The quick brown fox |
WideCaseInvert |
THE QUICK brown FOX |
Partial case change
To apply case changes to specific characters only, iterate character by character using WideCharUpper and WideCharLower. The built-in case functions operate on the entire string; this approach is needed when the logic depends on position or neighbouring characters.
The example below uppercases the first letter of each word while lowercasing all other letters, but deliberately leaves digits and punctuation untouched:
var
BaseName, Ext, NewName: WideString;
I: Integer;
AtWordStart: Boolean;
begin
BaseName := WideExtractBaseName(FileName);
Ext := WideExtractFileExt(FileName);
NewName := '';
AtWordStart := True;
for I := 1 to WideLength(BaseName) do
begin
if IsWideCharSpace(BaseName[I]) or IsWideCharPunct(BaseName[I]) then
begin
NewName := NewName + BaseName[I];
AtWordStart := True;
end
else if IsWideCharAlpha(BaseName[I]) then
begin
if AtWordStart then
NewName := NewName + WideCharUpper(BaseName[I])
else
NewName := NewName + WideCharLower(BaseName[I]);
AtWordStart := False;
end
else
begin
NewName := NewName + BaseName[I]; // digits and other chars pass through unchanged
AtWordStart := False;
end;
end;
FileName := NewName + Ext;
end.
Splitting and rearranging parts
WideSplitString and WideJoinStrings are the primary tools for decomposing a filename around a delimiter and reassembling it in a different order.
Consider files named "Artist - Title.mp3" where the goal is to swap artist and title to produce "Title - Artist.mp3":
var
Parts: TWideStringArray;
BaseName, Ext: WideString;
begin
BaseName := WideExtractBaseName(FileName);
Ext := WideExtractFileExt(FileName);
Parts := WideSplitString(BaseName, ' - ');
if Length(Parts) = 2 then
FileName := WideTrim(Parts[1]) + ' - ' + WideTrim(Parts[0]) + Ext;
end.
The delimiter passed to WideSplitString can be a multi-character string. WideTrim removes any stray whitespace from each part. The Length(Parts) = 2 guard ensures the script acts only when the expected structure is present.
WideJoinStrings reassembles an array with a delimiter inserted between each item:
var
Parts: TWideStringArray;
BaseName, Ext: WideString;
begin
BaseName := WideExtractBaseName(FileName);
Ext := WideExtractFileExt(FileName);
Parts := WideSplitString(BaseName, '_');
FileName := WideJoinStrings(Parts, ' ') + Ext;
end.
This replaces all underscores in the base name with spaces.
String manipulation
For targeted operations that do not split the whole name, use WidePos, WideCopy, WideInsert, WideDelete, and WideReplaceStr.
Finding and replacing text
To replace all occurrences of a substring (case-sensitive):
begin
FileName := WideReplaceStr(FileName, '_', ' ');
end.
For case-insensitive replacement, use WideReplaceText instead.
Moving a portion of the filename
To move the first N characters from the front of the base name to the end — for example, relocating a leading year "2024 Concert Recording" → "Concert Recording 2024":
var
BaseName, Ext, Prefix: WideString;
begin
BaseName := WideExtractBaseName(FileName);
Ext := WideExtractFileExt(FileName);
Prefix := WideCopy(BaseName, 1, 5); // "2024 "
WideDelete(BaseName, 1, 5);
FileName := WideTrim(BaseName) + ' ' + WideTrim(Prefix) + Ext;
end.
WideCopy extracts a substring by start position and character count. WideDelete removes characters from the string in place. Both use 1-based indexing.
Inserting text at a specific position
WideInsert modifies the target string in place, inserting text at the given 1-based position. A practical use is inserting a separator after a numeric prefix — for example, turning "007Skyfall" into "007 - Skyfall":
var
BaseName, Ext: WideString;
I: Integer;
begin
BaseName := WideExtractBaseName(FileName);
Ext := WideExtractFileExt(FileName);
// Find where the leading digits end
I := 1;
while (I <= WideLength(BaseName)) and IsWideCharDigit(BaseName[I]) do
Inc(I);
// Insert separator only if digits were found and something follows
if (I > 1) and (I <= WideLength(BaseName)) then
WideInsert(' - ', BaseName, I);
FileName := BaseName + Ext;
end.
Separating CamelCase words
This example inserts a space before each uppercase letter that follows a lowercase letter, turning CamelCase names like "TheEverlyBrothers" into "The Everly Brothers":
var
BaseName, Ext, NewName: WideString;
I: Integer;
begin
BaseName := WideExtractBaseName(FileName);
Ext := WideExtractFileExt(FileName);
NewName := '';
for I := 1 to WideLength(BaseName) do
begin
if (I > 1) and IsWideCharUpper(BaseName[I]) and IsWideCharLower(BaseName[I - 1]) then
NewName := NewName + ' ';
NewName := NewName + BaseName[I];
end;
FileName := NewName + Ext;
end.
The condition inserts a space only when the current character is uppercase and the preceding character is lowercase, targeting genuine CamelCase boundaries.
Initializing variables
Script-level variables persist across file iterations within a single preview run. This is intentional — it makes counters and accumulators possible — but it means any variable that should start fresh with each preview must be explicitly reset.
The recommended approach is a dedicated Initialized boolean flag. Since boolean variables default to False, the very first execution of the script naturally triggers initialisation, regardless of which file in the list is processed first:
var
Initialized: Boolean;
Counter: Integer;
Prefix: WideString;
begin
if not Initialized then
begin
Counter := 0;
Prefix := 'Item';
Initialized := True;
end;
Inc(Counter);
FileName := Prefix + ' ' + IntToStr(Counter) + ' - ' + FileName;
end.
An alternative sometimes seen is checking GetCurrentFileIndex = 1 (or GetCurrentMarkedFileIndex = 1). However, this is unreliable: if the first file in the list is unmarked (excluded from renaming), the script skips it and the index for the first executed file will be greater than 1, so the initialisation block never runs. The Initialized flag avoids this problem entirely and is generally preferred.
Script-level variables are reset at the start of every Preview operation — the script is compiled fresh each time Preview runs. They do not persist between previews. For data that must outlast a single preview, see the Persisting data section.
Serializing files
To add an incrementing index to each filename, use GetCurrentMarkedFileIndex combined with FormatFloat for zero-padded output:
begin
FileName := FormatFloat('000', GetCurrentMarkedFileIndex) + ' - ' + FileName;
end.
GetCurrentMarkedFileIndex counts only the files actually being processed (marked for renaming), so the index always reflects the position in the renamed set rather than the full file list. FormatFloat('000', ...) pads the number to at least three digits (e.g. 001, 012, 123). Adjust the number of zeroes to control the minimum width.
To start from a custom valueindex (e.g. 100 rather than 1:1) and use Roman numerals instead of decimal numbers:
var
Index: Integer;
begin
Index := GetCurrentMarkedFileIndex + 99; // starts from 100
FileName := IntToStr(IntToRoman(Index) + ' - ' + FileName;
end.
Generating random names
Generating a random name can be useful for anonymising files, creating unique temporary identifiers, or testing.
The RandomString function builds a string of a given length by picking characters at random from a supplied alphabet. RandomRange(6, 13) produces random lengths between 6 and 12 characters. Randomize seeds the random number generator and should be called only once per session.
const
Alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789';
var
Initialized: Boolean;
begin
if not Initialized then
begin
Randomize;
Initialized := True;
end;
FileName := RandomString(RandomRange(6, 13), Alphabet)
+ WideExtractFileExt(FileName);
end.
Indexing per folder
When files from multiple folders are loaded, you may want the index to reset for each folder. The script below tracks the active folder and resets a local counter whenever it changes:
var
Initialized: Boolean;
CurrentFolder: WideString;
FolderIndex: Integer;
begin
if not Initialized then
begin
CurrentFolder := '';
FolderIndex := 0;
Initialized := True;
end;
if WideExtractFileDir(FilePath) <> CurrentFolder then
begin
CurrentFolder := WideExtractFileDir(FilePath);
FolderIndex := 0;
end;
Inc(FolderIndex);
FileName := FormatFloat('00', FolderIndex) + ' - ' + FileName;
end.
The Initialized block resets the tracking variables at the start of each preview, ensuring the counter restarts correctly every time.
Serializing duplicates
When multiple files would otherwise produce the same new name, a counter suffix can disambiguate them, producing "Name.ext", "Name (2).ext", "Name (3).ext", and so on.
The script maintains an array of names already assigned in the current preview run and checks each candidate against it:
var
Initialized: Boolean;
SeenNames: TWideStringArray;
BaseName, Ext, Candidate: WideString;
Counter, I: Integer;
Found: Boolean;
begin
if not Initialized then
begin
SetLength(SeenNames, 0);
Initialized := True;
end;
BaseName := WideExtractBaseName(FileName);
Ext := WideExtractFileExt(FileName);
Candidate := FileName;
Counter := 1;
repeat
Found := False;
for I := 0 to Length(SeenNames) - 1 do
begin
if WideSameText(SeenNames[I], Candidate) then
begin
Found := True;
Break;
end;
end;
if Found then
begin
Inc(Counter);
Candidate := BaseName + ' (' + IntToStr(Counter) + ')' + Ext;
end;
until not Found;
WideAppendStringArray(SeenNames, Candidate);
FileName := Candidate;
end.
WideSameText performs a case-insensitive comparison, which is appropriate since Windows filenames are case-insensitive. WideAppendStringArray adds the finalised name to the list before moving on to the next file.
Controlling script flow
Use Exit to skip renaming for the current file without an error — FileName is left unchanged and the file passes through untouched:
begin
if WideExtractFileExt(FileName) = '.tmp' then
Exit;
FileName := 'Processed_' + FileName;
end.
To stop processing all subsequent files once a condition is met, use a persistent boolean flag together with Exit. The flag persists across iterations within the same preview run:
var
StopProcessing: Boolean;
begin
if StopProcessing then
Exit;
if WideDialogYesNo('Rename "' + FileName + '"?') then
FileName := 'Processed_' + FileName
else
StopProcessing := True;
end.
The first time the user clicks No, StopProcessing is set to True and Exit is called for every subsequent file, leaving them all unchanged.
Renaming by file date and time
To rename a file using its last-modified timestamp, read it with FileTimeModified and format it with FormatDateTime:
var
FileTime: TDateTime;
begin
FileTime := FileTimeModified(FilePath);
FileName := FormatDateTime('yyyy-mm-dd hh-nn-ss', FileTime) + WideExtractFileExt(FileName);
end.
FormatDateTime accepts a format string following the Date and Time format conventions. Note that minutes use nn to distinguish them from months (mm). The example above produces names like 2024-06-15 14-32-07.jpg.
To use the creation time instead, replace FileTimeModified with FileTimeCreated.
For scripts that need to adjust a date embedded in the filename, parse it with TryScanDateTime, modify it with one of the Inc* functions, then reformat:
var
FileDate: TDateTime;
BaseName, Ext: WideString;
begin
BaseName := WideExtractBaseName(FileName);
Ext := WideExtractFileExt(FileName);
if TryScanDateTime('yyyy-mm-dd hh-nn-ss', BaseName, FileDate) then
begin
FileDate := IncHour(FileDate, 2);
FileName := FormatDateTime('yyyy-mm-dd hh-nn-ss', FileDate) + Ext;
end;
end.
TryScanDateTime returns False rather than raising an error if the string does not match the pattern, making it safe to use without additional guards.
Adjusting dates embedded in filenames
Cameras and recording devices sometimes embed timestamps in filenames (e.g. 2024-06-15 14-32-07.jpg). When the device clock was set to the wrong timezone, or when DST was not accounted for, every file in a batch needs the same time offset applied.
TryScanDateTime parses the embedded date according to a format pattern, IncHour shifts it by the required amount — handling all day, month, and year rollovers automatically — and FormatDateTime writes it back:
const
InputFormat = 'yyyy-mm-dd hh-nn-ss';
OutputFormat = 'yyyy-mm-dd hh-nn-ss';
HoursToAdd = -3; // use a negative value to subtract
var
BaseName, Ext: WideString;
DateTime: TDateTime;
begin
BaseName := WideExtractBaseName(FileName);
Ext := WideExtractFileExt(FileName);
if TryScanDateTime(InputFormat, BaseName, DateTime) then
begin
DateTime := IncHour(DateTime, HoursToAdd);
FileName := FormatDateTime(OutputFormat, DateTime) + Ext;
end;
end.
TryScanDateTime returns False without raising an error if the filename does not match the expected pattern, so files with non-conforming names are left unchanged.
The input and output format strings are kept as separate constants, making it straightforward to reformat while adjusting — for example, changing OutputFormat to 'yyyy-mm-dd hh.nn.ss' converts the time separators from dashes to dots in the same pass. See the Date and Time format reference for format specifiers.
Reading file content
Scripts can read file content directly, which is useful for assigning names based on data inside the file itself.
Reading a fixed fragment
FileReadFragment reads a specific number of bytes from a given offset (0-based). This is efficient for sampling file headers without loading the entire file:
var
Header: String;
begin
Header := FileReadFragment(FilePath, 0, 4);
if Copy(Header, 1, 2) = 'PK' then
FileName := '[ZIP] ' + FileName;
end.
Reading the full content
FileReadText returns the entire file as a WideString and automatically detects encoding via byte order mark (BOM), supporting UTF-8 and UTF-16. Files without BOM are treated as ANSI.
A practical use is extracting a value embedded inside the file. The example below renames HTML files using the text content of their <title> tag:
var
Content, Title: WideString;
PosStart, PosEnd: Integer;
begin
if not WideSameText(WideExtractFileExt(FileName), '.html') then
Exit;
Content := FileReadText(FilePath);
PosStart := WideTextPos('<title>', Content);
PosEnd := WideTextPos('</title>', Content);
if (PosStart > 0) and (PosEnd > PosStart) then
begin
PosStart := PosStart + 7; // skip past '<title>'
Title := WideTrim(WideCopy(Content, PosStart, PosEnd - PosStart));
if Title <> '' then
FileName := Title + '.html';
end;
end.
WideTextPos performs a case-insensitive search, which is appropriate for HTML tags. WideCopy extracts the text between the two tag positions, and WideTrim cleans up any surrounding whitespace.
Renaming from a list
A plain text file with one new name per line can drive batch renaming. Load the list once on first run, then advance a local counter on each subsequent execution:
var
Initialized: Boolean;
Names: TWideStringArray;
Counter: Integer;
begin
if not Initialized then
begin
Names := FileReadTextLines(WideGetEnvironmentVar('USERPROFILE') + '\Desktop\names.txt');
Counter := 0;
Initialized := True;
end;
if Counter < Length(Names) then
begin
FileName := WideTrim(Names[Counter]) + WideExtractFileExt(FileName);
Inc(Counter);
end;
end.
FileReadTextLines handles encoding detection automatically. Counter starts at 0 to match the zero-based array index and is incremented after each use. Files beyond the end of the list are left unchanged.
Converting file content encoding
Scripts are not limited to renaming — they can also modify the content of the files being processed. A practical use is bulk-converting text files from the Windows system code page (ANSI) to UTF-8, which is required when moving content to systems or editors that expect UTF-8.
Warning: This script writes directly to the original file on disk during the Preview stage. The change is immediate and irreversible. Always keep backups before running it on a large batch, and verify the result on a single file first.
The script assembles the UTF-8 BOM (EF BB BF) from its hex byte values, checks whether the file already starts with it, and skips the file if so — making the script safe to run more than once on the same set of files:
const
UTF8_BOM = #$EF#$BB#$BF;
var
Content: String;
begin
Content := FileReadContent(FilePath);
// Skip files that are already UTF-8 encoded (BOM present)
if Copy(Content, 1, 3) = UTF8_BOM then
Exit;
Content := WinCPToUTF8(Content);
FileWriteContent(FilePath, UTF8_BOM + Content);
end.
FileReadContent reads the raw bytes of the file as a String. WinCPToUTF8 re-encodes the string from the active Windows system code page to UTF-8. The BOM is then prepended before writing the result back with FileWriteContent, which overwrites the original file.
Note that FileName is not modified in this script. The script's sole effect is on the file content. This is unusual behaviour for a Pascal Script rule and worth keeping in mind when combining it with other rules in the same preset.
Interactive dialogs
Dialog functions pause the preview run and prompt the user, making it possible to inject values or confirm decisions at runtime. Because scripts run for each file in sequence, dialogs should generally be shown only once per preview — use the Initialized flag pattern for this — unless per-file confirmation is specifically intended.
Displaying messages
WideShowMessage is useful during development for inspecting intermediate values:
begin
WideShowMessage('File: ' + FileName + #13#10 + 'Path: ' + FilePath);
end.
#13#10 is a carriage return and line feed, producing a newline inside the message box.
Yes/No confirmation per file
WideDialogYesNo pauses for user input and returns True if the user clicks Yes:
begin
if WideDialogYesNo('Apply uppercase to:' + #13#10 + FileName) then
FileName := WideUpperCase(FileName);
end.
Collecting input once per preview
WideInputQuery shows a text input dialog. It returns True if the user clicked OK, and the entered text is available via the var parameter:
var
Initialized: Boolean;
Prefix: WideString;
OK: Boolean;
begin
if not Initialized then
begin
OK := WideInputQuery('Prefix', 'Enter a prefix to add to all filenames:', Prefix);
if not OK then
Exit;
Initialized := True;
end;
FileName := Prefix + FileName;
end.
The dialog appears once, the user types a prefix, and that value is reused for every file in the list.
Regular expressions
The built-in regex functions bring pattern matching directly into scripts, useful for transformations that are too complex for a simple search-and-replace.
Pattern-based replacement
ReplaceRegEx works like the Regular Expressions rule but within a script, allowing the result to be further processed:
begin
// Collapse any sequence of whitespace into a single space
FileName := ReplaceRegEx(FileName, '\s+', ' ', False, False);
FileName := WideTrim(FileName);
end.
With UseSubstitution set to True, backreferences can be used in the replacement pattern. The example below moves a trailing four-digit year to the front:
begin
// "Concert Recording 2024.mp4" -> "2024 Concert Recording.mp4"
FileName := ReplaceRegEx(FileName, '^(.*)\s(\d{4})(\.[^.]+)$', '$2 $1$3', True, True);
end.
Extracting matches
MatchesRegEx returns an array of all substrings in the input that match the given pattern:
var
Matches: TWideStringArray;
Ext: WideString;
begin
Ext := WideExtractFileExt(FileName);
Matches := MatchesRegEx(FileName, '\d+', True);
if Length(Matches) > 0 then
FileName := 'Track_' + Matches[0] + Ext;
end.
Applying case conversion to matched groups
SubMatchesRegEx returns the captured groups from the first full match. Combined with MatchesRegEx, it allows case functions to be applied selectively to specific parts of the name:
var
Matches: TWideStringArray;
I: Integer;
begin
// Capitalise each word in the base name
Matches := MatchesRegEx(WideExtractBaseName(FileName), '[a-zA-Z]+', False);
for I := 0 to Length(Matches) - 1 do
FileName := WideReplaceStr(FileName, Matches[I], WideCaseCapitalize(Matches[I]));
end.
Persisting data
Script-level variables reset at the start of every Preview. Two mechanisms are available when data must outlast a single preview run.
Global Variables
Global Variables (added in v7.4.0.2) persist across previews for the entire application session — until manually cleared or the application is closed. They are identified by name and can store any value type.
var
Initialized: Boolean;
RunCount: Integer;
begin
if not Initialized then
begin
if HasGlobalVar('RunCount') then
RunCount := GetGlobalVar('RunCount')
else
RunCount := 0;
Initialized := True;
end;
Inc(RunCount);
SetGlobalVar('RunCount', RunCount);
FileName := FormatFloat('0000', RunCount) + '_' + FileName;
end.
To reset all global variables at the start of each preview, clear them inside the Initialized block:
begin
if not Initialized then
begin
ClearGlobalVars;
Initialized := True;
end;
// ... rest of script
end.
File-based persistence
For data that must survive application restarts, write it to a file. Using the system temporary folder keeps the file out of the way:
var
Initialized: Boolean;
StorageFile: WideString;
Counter: Integer;
begin
StorageFile := WideGetTempPath + 'renamer_counter.txt';
if not Initialized then
begin
if WideFileExists(StorageFile) then
Counter := StrToIntDef(FileReadContent(StorageFile), 0)
else
Counter := 0;
Initialized := True;
end;
Inc(Counter);
FileWriteContent(StorageFile, IntToStr(Counter));
FileName := FormatFloat('0000', Counter) + '_' + FileName;
end.
Delete the storage file manually (or from a cleanup script) to reset the counter. StrToIntDef returns the default value 0 if the file contains unexpected content, preventing a script error.
Using meta tags
The CalculateMetaTag function extracts metadata from a file using ReNamer's built-in Meta Tags engine, covering EXIF, ID3, document properties, and more. The full list of available tag names is on the Meta Tags reference page.
A common use is renaming photos by their EXIF capture date:
begin
FileName := CalculateMetaTag(FilePath, 'EXIF_Date') + WideExtractFileExt(FileName);
end.
The date format used here follows the application's default Date and Time format setting. To use a specific format regardless of that setting, use CalculateMetaTagFormat:
begin
FileName := CalculateMetaTagFormat(FilePath, 'EXIF_Date', 'yyyy-mm-dd') + WideExtractFileExt(FileName);
end.
For more control over how the date is assembled — for instance, combining it with other parts of the name — retrieve the timestamp as a raw TDateTime value using FileTimeModified or FileTimeCreated and format it manually with FormatDateTime. See the Renaming by file date and time section for that approach.
It is good practice to guard against an empty result, which occurs when the tag is not present in the file:
var
Tag: WideString;
begin
Tag := CalculateMetaTag(FilePath, 'ID3_Title');
if Tag <> '' then
FileName := Tag + WideExtractFileExt(FileName);
end.
User-defined functions
Scripts can define their own functions and procedures (UDFs), which is useful for encapsulating reusable logic and keeping the main code block readable. Declarations must appear before the main code block.
The example below defines a helper that strips characters forbidden in Windows filenames:
function StripInvalidChars(const S: WideString): WideString;
var
I: Integer;
C: WideChar;
begin
Result := '';
for I := 1 to WideLength(S) do
begin
C := S[I];
if WidePos(C, '\/:*?"<>|') = 0 then
Result := Result + C;
end;
end;
begin
FileName := StripInvalidChars(FileName);
end.
Note that Result here is the standard Pascal return-value identifier used inside a function body — this is its correct and intended use. User-defined functions and procedures follow standard Pascal syntax; they can accept parameters, call other UDFs, and use all built-in functions.
Importing external functions
Scripts can call functions from any external DLL by declaring them with the external directive followed by an import declaration in one of these formats:
function FunctionName(Parameters): ReturnType; external 'FunctionName@Library.dll CallingConvention';procedure FunctionName(Parameters); external 'FunctionName@Library.dll CallingConvention';
A minimal example using a Windows API function:
function GetTickCount: Cardinal;
external 'GetTickCount@kernel32.dll stdcall';
begin
FileName := IntToStr(GetTickCount) + '_' + FileName;
end.
The example below retrieves the current Windows username:
function GetUserNameA(lpBuffer: PChar; var nSize: Cardinal): Boolean;
external 'GetUserNameA@advapi32.dll stdcall';
var
Size: Cardinal;
UserName: String;
begin
Size := 200;
SetLength(UserName, Size);
if GetUserNameA(PChar(UserName), Size) then
begin
SetLength(UserName, Size - 1); // Size includes trailing null terminator
FileName := UserName + '_' + FileName;
end;
end.
The calling convention is stdcall for most Windows API functions and cdecl for many C library functions. An incorrect declaration or a missing DLL will cause a script error. Test new imports carefully.
For integrations with 3rd party command-line tools such as ExifTool or MediaInfo, ExecConsoleApp is often more practical than DLL import — see the Library article for those scripts.
Conditional processing by file type
Scripts process every file in the list, but sometimes logic should apply only to a subset. WideMatchesMask tests a filename against a single wildcard mask; WideMatchesMaskList tests against a semicolon-separated list.
To apply a transformation only to image files:
begin
if not WideMatchesMaskList(FileName, '*.jpg;*.jpeg;*.png;*.gif;*.webp') then
Exit;
FileName := CalculateMetaTag(FilePath, 'EXIF_Date') + WideExtractFileExt(FileName);
end.
The Exit at the top is an efficient way to skip non-matching files early, leaving FileName unchanged. The same pattern applies whenever different rules should apply to different file types within a single script:
begin
if WideMatchesMask(FileName, '*.mp3') then
FileName := CalculateMetaTag(FilePath, 'ID3_Title') + '.mp3'
else if WideMatchesMask(FileName, '*.flac') then
FileName := CalculateMetaTag(FilePath, 'ID3_Title') + '.flac';
end.
URL-decoding filenames
Files downloaded from the web sometimes retain URL encoding in their names, producing strings like "My%20Document%20%282024%29.pdf". The built-in URLDecode function converts them back to readable form:
var
BaseName, Ext: WideString;
begin
BaseName := WideExtractBaseName(FileName);
Ext := WideExtractFileExt(FileName);
FileName := URLDecode(BaseName, False) + Ext;
end.
The second parameter, UsePlusAsSpace, controls whether + signs are treated as spaces. Set it to False for standard URL encoding (where spaces are %20), or True for HTML form encoding (where spaces are +).
The extension is decoded separately only when needed, since extensions are typically not URL-encoded.