Compare commits

..

No commits in common. "main" and "change-138" have entirely different histories.

21 changed files with 87 additions and 190 deletions

View file

@ -35,7 +35,6 @@ Add yourself to the list,
[donate with PayPal ↗️](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)!
* Abdul Mannan Abbasi
* Alex Maier
* Andrea Ruffini Perico
* Andrew Cox
* Christoph Boss (@Kampfwurst)

View file

@ -18,12 +18,9 @@ Run the complete base installation:
{
:local BaseUrl "https://git.eworm.de/cgit/routeros-scripts/plain/";
:local CertCommonName "ISRG Root X2";
:local CertFileName "ISRG-Root-X2.pem";
:local CertFingerprint "69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470";
:if (!(([ /certificate/settings/get ]->"builtin-trust-anchors") = "trusted" && \
[[ :parse (":return [ :len [ /certificate/builtin/find where common-name=\"" . $CertCommonName . "\" ] ]") ]] > 0)) do={
:put "Importing certificate...";
/tool/fetch ($BaseUrl . "certs/" . $CertFileName) dst-path=$CertFileName as-value;
:delay 1s;
@ -32,7 +29,6 @@ Run the complete base installation:
:error "Something is wrong with your certificates!";
};
:delay 1s;
};
:put "Renaming global-config-overlay, if exists...";
/system/script/set name=("global-config-overlay-" . [ /system/clock/get date ] . "-" . [ /system/clock/get time ]) [ find where name="global-config-overlay" ];
:foreach Script in={ "global-config"; "global-config-overlay"; "global-functions" } do={
@ -45,12 +41,10 @@ Run the complete base installation:
:put "Scheduling to load configuration and functions...";
/system/scheduler/remove [ find where name="global-scripts" ];
/system/scheduler/add name="global-scripts" start-time=startup on-event="/system/script { run global-config; run global-functions; }";
:if ([ :len [ /certificate/find where fingerprint=$CertFingerprint ] ] > 0) do={
:put "Renaming certificate by its common-name...";
:global CertificateNameByCN;
$CertificateNameByCN $CertFingerprint;
};
};
Then continue setup with
[scheduled automatic updates](README.md#scheduled-automatic-updates) or

View file

@ -72,15 +72,7 @@ including demonstation recorded live at [MUM Europe
### The long way in detail
The update script does server certificate verification, so first step is to
download the certificates.
> 💡️ **Hint**: RouterOS 7.19 comes with a builtin certificate store. You
> can skip the steps regarding certificate download and import and jump
> to [installation of scripts](#installation-of-scripts) if you set the
> trust for these builtin trust anchors:
> `/certificate/settings/set builtin-trust-anchors=trusted;`
If you intend to download the scripts from a
download the certificates. If you intend to download the scripts from a
different location (for example from github.com) install the corresponding
certificate chain.
@ -114,8 +106,6 @@ is shown.
Always make sure there are no certificates installed you do not know or want!
#### Installation of scripts
All following commands will verify the server certificate. For validity the
certificate's lifetime is checked with local time, so make sure the device's
date and time is set correctly!

View file

@ -22,7 +22,7 @@
:foreach AccList in=[ /caps-man/access-list/find where mac-address!="00:00:00:00:00:00" ] do={
:local Mac [ /caps-man/access-list/get $AccList mac-address ];
:if ($Seen->$Mac = 1) do={
/caps-man/access-list/print without-paging where mac-address=$Mac;
/caps-man/access-list/print where mac-address=$Mac;
:local Remove [ :tonum [ /terminal/ask prompt="\nNumeric id to remove, any key to skip!" ] ];
:if ([ :typeof $Remove ] = "num") do={

View file

@ -22,7 +22,7 @@
:foreach AccList in=[ /interface/wireless/access-list/find where mac-address!="00:00:00:00:00:00" ] do={
:local Mac [ /interface/wireless/access-list/get $AccList mac-address ];
:if ($Seen->$Mac = 1) do={
/interface/wireless/access-list/print without-paging where mac-address=$Mac;
/interface/wireless/access-list/print where mac-address=$Mac;
:local Remove [ :tonum [ /terminal/ask prompt="\nNumeric id to remove, any key to skip!" ] ];
:if ([ :typeof $Remove ] = "num") do={

View file

@ -27,9 +27,9 @@
:local Mac [ /interface/wifi/access-list/get $AccList mac-address ];
:local Mac [ /interface/wireless/access-list/get $AccList mac-address ];
:if ($Seen->$Mac = 1) do={
/caps-man/access-list/print without-paging where mac-address=$Mac;
/interface/wifi/access-list/print without-paging where mac-address=$Mac;
/interface/wireless/access-list/print without-paging where mac-address=$Mac;
/caps-man/access-list/print where mac-address=$Mac;
/interface/wifi/access-list/print where mac-address=$Mac;
/interface/wireless/access-list/print where mac-address=$Mac;
:local Remove [ :tonum [ /terminal/ask prompt="\nNumeric id to remove, any key to skip!" ] ];
:if ([ :typeof $Remove ] = "num") do={

View file

@ -22,7 +22,7 @@
:foreach AccList in=[ /interface/wifi/access-list/find where mac-address!="00:00:00:00:00:00" ] do={
:local Mac [ /interface/wifi/access-list/get $AccList mac-address ];
:if ($Seen->$Mac = 1) do={
/interface/wifi/access-list/print without-paging where mac-address=$Mac;
/interface/wifi/access-list/print where mac-address=$Mac;
:local Remove [ :tonum [ /terminal/ask prompt="\nNumeric id to remove, any key to skip!" ] ];
:if ([ :typeof $Remove ] = "num") do={

View file

@ -27,7 +27,6 @@
:global CleanName;
:global DeviceInfo;
:global FileExists;
:global FormatLine;
:global LogPrint;
:global MkDir;
@ -125,19 +124,17 @@
attach=$Attach; remove-attach=true });
# wait for the mail to be sent
:do {
:retry {
:if ([ $FileExists ($FilePath . ".conf") ".conf file" ] = true || \
[ $FileExists ($FilePath . ".backup") "backup" ] = true || \
[ $FileExists ($FilePath . ".rsc") "script" ] = true) do={
:error "Files are still available.";
}
} delay=1s max=120;
} on-error={
:local I 0;
:while ([ :len [ /file/find where name ~ ($FilePath . "\\.(backup|rsc)\$") ] ] > 0) do={
:if ($I >= 120) do={
$LogPrint warning $ScriptName ("Files are still available, sending e-mail failed.");
:set PackagesUpdateBackupFailure true;
:set ExitOK true;
:error false;
}
:delay 1s;
:set I ($I + 1);
}
# do not remove the files here, as the mail is still queued!
} do={
:global ExitError; $ExitError $ExitOK [ :jobname ] $Err;
}

View file

@ -20,7 +20,6 @@
:global CleanFilePath;
:global DownloadPackage;
:global FileGet;
:global LogPrint;
:global MkDir;
:global RmFile;
@ -43,7 +42,7 @@
:error false;
}
:if ([ $FileGet $PackagePath ] = false) do={
:if ([ :len [ /file/find where name=$PackagePath type="directory" ] ] = 0) do={
:if ([ $MkDir $PackagePath ] = false) do={
$LogPrint warning $ScriptName ("Creating directory at CAPsMAN package path (" . \
$PackagePath . ") failed!");
@ -54,7 +53,7 @@
"). Please place your packages!");
}
:foreach Package in=[ /file/find where type="package" \
:foreach Package in=[ /file/find where type=package \
package-version!=$InstalledVersion name~("^" . $PackagePath) ] do={
:local File [ /file/get $Package ];
:if ($File->"package-architecture" = "mips") do={
@ -67,7 +66,7 @@
}
}
:if ([ :len [ /file/find where type="package" name~("^" . $PackagePath) ] ] = 0) do={
:if ([ :len [ /file/find where type=package name~("^" . $PackagePath) ] ] = 0) do={
$LogPrint info $ScriptName ("No packages available, downloading default set.");
:foreach Arch in={ "arm"; "mipsbe" } do={
:foreach Package in={ "routeros"; "wireless" } do={

View file

@ -21,7 +21,6 @@
:global CleanFilePath;
:global DownloadPackage;
:global FileGet;
:global LogPrint;
:global MkDir;
:global RmFile;
@ -45,7 +44,7 @@
:error false;
}
:if ([ $FileGet $PackagePath ] = false) do={
:if ([ :len [ /file/find where name=$PackagePath type="directory" ] ] = 0) do={
:if ([ $MkDir $PackagePath ] = false) do={
$LogPrint warning $ScriptName ("Creating directory at CAPsMAN package path (" . \
$PackagePath . ") failed!");
@ -56,7 +55,7 @@
"). Please place your packages!");
}
:foreach Package in=[ /file/find where type="package" \
:foreach Package in=[ /file/find where type=package \
package-version!=$InstalledVersion name~("^" . $PackagePath) ] do={
:local File [ /file/get $Package ];
:if ($File->"package-architecture" = "mips") do={
@ -69,7 +68,7 @@
}
}
:if ([ :len [ /file/find where type="package" name~("^" . $PackagePath) ] ] = 0) do={
:if ([ :len [ /file/find where type=package name~("^" . $PackagePath) ] ] = 0) do={
$LogPrint info $ScriptName ("No packages available, downloading default set.");
# NOT /interface/wifi/ #
:foreach Arch in={ "arm"; "mipsbe" } do={

View file

@ -20,7 +20,6 @@
:global CleanFilePath;
:global DownloadPackage;
:global FileGet;
:global LogPrint;
:global MkDir;
:global RmFile;
@ -43,7 +42,7 @@
:error false;
}
:if ([ $FileGet $PackagePath ] = false) do={
:if ([ :len [ /file/find where name=$PackagePath type="directory" ] ] = 0) do={
:if ([ $MkDir $PackagePath ] = false) do={
$LogPrint warning $ScriptName ("Creating directory at CAPsMAN package path (" . \
$PackagePath . ") failed!");
@ -54,7 +53,7 @@
"). Please place your packages!");
}
:foreach Package in=[ /file/find where type="package" \
:foreach Package in=[ /file/find where type=package \
package-version!=$InstalledVersion name~("^" . $PackagePath) ] do={
:local File [ /file/get $Package ];
:if ($File->"package-architecture" = "mips") do={
@ -67,7 +66,7 @@
}
}
:if ([ :len [ /file/find where type="package" name~("^" . $PackagePath) ] ] = 0) do={
:if ([ :len [ /file/find where type=package name~("^" . $PackagePath) ] ] = 0) do={
$LogPrint info $ScriptName ("No packages available, downloading default set.");
:foreach Arch in={ "arm"; "arm64" } do={
:local Packages { "arm"={ "routeros"; "wifi-qcom"; "wifi-qcom-ac" };

View file

@ -28,7 +28,6 @@
:global EscapeForRegEx;
:global FetchUserAgentStr;
:global LogPrint;
:global RebootForUpdate;
:global ScriptFromTerminal;
:global ScriptLock;
:global SendNotification2;
@ -63,15 +62,10 @@
$WaitFullyConnected;
:if ([ :len [ /system/scheduler/find where name="_RebootForUpdate" ] ] > 0) do={
:if ([ :typeof $RebootForUpdate ] = "nothing") do={
$LogPrint info $ScriptName ("Found a stale scheduler for reboot, removing.");
/system/scheduler/remove "_RebootForUpdate";
} else={
$LogPrint info $ScriptName ("A reboot for update is already scheduled.");
:set ExitOK true;
:error false;
}
}
$LogPrint debug $ScriptName ("Checking for updates...");
/system/package/update/check-for-updates without-paging as-value;

View file

@ -33,9 +33,6 @@ certificate is checked.
> ⚠️ **Warning**: The script does not limit the size of a list, but keep in
> mind that huge lists can exhaust your device's resources (RAM and CPU),
> and may take a long time to process.
> Even crashes for the complete scripting (and CLI) subsystem are possible.
> This should be logged accordingly with warnings when global functions are
> reloaded from scheduler.
Requirements and installation
-----------------------------

View file

@ -46,8 +46,8 @@ Configuration
The configuration goes to `global-config-overlay`, this is the only parameter:
* `PackagesUpdateDeferReboot`: defer the reboot for night (between 3 AM and
5 AM), use a numerical value in days suffixed with a `d` to defer further
* `PackagesUpdateDeferReboot`: defer the reboot for night (between 3 AM
and 5 AM)
By modifying the scheduler's `start-time` you can force the reboot at
different time.

View file

@ -48,12 +48,6 @@
}
$WaitFullyConnected;
:if ([ :len [ /log/find where topics=({"script"; "warning"}) \
message=("\$LogPrintOnce: The message is already in log, scripting subsystem may have crashed before!") ] ] > 0) do={
$LogPrintOnce warning $ScriptName ("Scripting subsystem may have crashed, possibly caused by us. Delaying!");
:delay 5m;
}
:local ListComment ("managed by " . $ScriptName);
:foreach FwListName,FwList in=$FwAddrLists do={

View file

@ -38,8 +38,6 @@
:global ExitError;
:global FetchHuge;
:global FetchUserAgentStr;
:global FileExists;
:global FileGet;
:global FormatLine;
:global FormatMultiLines;
:global GetMacVendor;
@ -121,11 +119,6 @@
:return false;
}
:if (([ /certificate/settings/get ]->"builtin-trust-anchors") = "trusted" && \
[[ :parse (":return [ :len [ /certificate/builtin/find where common-name=\"" . $CommonName . "\" ] ]") ]] > 0) do={
:return true;
}
:if ([ :len [ /certificate/find where common-name=$CommonName ] ] = 0) do={
$LogPrint info $0 ("Certificate with CommonName '" . $CommonName . "' not available.");
:if ([ $CertificateDownload $CommonName ] = false) do={
@ -365,7 +358,6 @@
:global CertificateAvailable;
:global CleanFilePath;
:global FileExists;
:global LogPrint;
:global MkDir;
:global RmFile;
@ -386,7 +378,7 @@
:return false;
}
:if ([ $FileExists $PkgDest "package" ] = true) do={
:if ([ :len [ /file/find where name=$PkgDest type="package" ] ] > 0) do={
$LogPrint info $0 ("Package file " . $PkgName . " already exists.");
:return true;
}
@ -408,7 +400,7 @@
:return false;
}
:if ([ $FileExists $PkgDest "package" ] = false) do={
:if ([ /file/get [ find where name=$PkgDest ] type ] != "package") do={
$LogPrint warning $0 ("Downloaded file is not a package, removing.");
$RmFile $PkgDest;
:return false;
@ -532,47 +524,6 @@
$Resource->"architecture-name" . " " . $Caller . "/Fetch (https://rsc.eworm.de/)");
}
# check for existence of file, optionally with type
:set FileExists do={
:local FileName [ :tostr $1 ];
:local Type [ :tostr $2 ];
:global FileGet;
:local FileVal [ $FileGet $FileName ];
:if ($FileVal = false) do={
:return false;
}
:if ([ :len ($FileVal->"size") ] = 0) do={
:return false;
}
:if ([ :len $Type ] = 0 || $FileVal->"type" = $Type) do={
:return true;
}
:return false;
}
# get file properties in array, or false on error
:set FileGet do={
:local FileName [ :tostr $1 ];
:global WaitForFile;
:if ([ $WaitForFile $FileName 0s ] = false) do={
:return false;
}
:local FileVal false;
:do {
:set FileVal [ /file/get $FileName ];
} on-error={ }
:return $FileVal;
}
# format a line for output
:set FormatLine do={
:local Key [ :tostr $1 ];
@ -924,7 +875,6 @@
:local Path [ :tostr $1 ];
:global CleanFilePath;
:global FileGet;
:global LogPrint;
:global RmDir;
:global WaitForFile;
@ -962,8 +912,7 @@
$LogPrint debug $0 ("Making directory: " . $Path);
:local PathVal [ $FileGet $Path ];
:if ($PathVal->"type" = "directory") do={
:if ([ :len [ /file/find where name=$Path type="directory" ] ] = 1) do={
$LogPrint debug $0 ("... which already exists.");
:return true;
}
@ -1088,26 +1037,25 @@
:set RmDir do={
:local DirName [ :tostr $1 ];
:global FileGet;
:global LogPrint;
$LogPrint debug $0 ("Removing directory: ". $DirName);
:local DirVal [ $FileGet $DirName ];
:if ($DirVal = false) do={
$LogPrint debug $0 ("... which does not exist.");
:return true;
}
:if ($DirVal->"type" != "directory") do={
:if ([ :len [ /file/find where name=$DirName type!=directory ] ] > 0) do={
$LogPrint error $0 ("Directory '" . $DirName . "' is not a directory.");
:return false;
}
:local Dir [ /file/find where name=$DirName type=directory ];
:if ([ :len $Dir ] = 0) do={
$LogPrint debug $0 ("... which does not exist.");
:return true;
}
:onerror Err {
/file/remove $DirName;
/file/remove $Dir;
} do={
$LogPrint error $0 ("Removing directory '" . $DirName . "' failed: " . $Err);
$LogPrint error $0 ("Removing directory '" . $DirName . "' (" . $Dir . ") failed: " . $Err);
:return false;
}
:return true;
@ -1117,26 +1065,25 @@
:set RmFile do={
:local FileName [ :tostr $1 ];
:global FileGet;
:global LogPrint;
$LogPrint debug $0 ("Removing file: ". $FileName);
:local FileVal [ $FileGet $FileName ];
:if ($FileVal = false) do={
$LogPrint debug $0 ("... which does not exist.");
:return true;
}
:if ($FileVal->"type" = "directory" || $FileVal->"type" = "disk") do={
:if ([ :len [ /file/find where name=$FileName (type=directory or type=disk) ] ] > 0) do={
$LogPrint error $0 ("File '" . $FileName . "' is not a file.");
:return false;
}
:local File [ /file/find where name=$FileName !(type=directory or type=disk) ];
:if ([ :len $File ] = 0) do={
$LogPrint debug $0 ("... which does not exist.");
:return true;
}
:onerror Err {
/file/remove $FileName;
/file/remove $File;
} do={
$LogPrint error $0 ("Removing file '" . $FileName . "' failed: " . $Err);
$LogPrint error $0 ("Removing file '" . $FileName . "' (" . $File . ") failed: " . $Err);
:return false;
}
:return true;
@ -1265,7 +1212,7 @@
:set SourceNew [ :tolf ($Result->"data") ];
}
} do={
$LogPrint warning $0 ("Failed fetching script '" . $ScriptVal->"name" . "': " . $Err);
$LogPrint warning $0 ("Failed fetching script '" . $ScriptVal->"name" . . "': " . $Err);
:if ($ScriptVal->"source" = "#!rsc by RouterOS\n") do={
$LogPrint warning $0 ("Removing dummy. Typo on installation?");
/system/script/remove $Script;
@ -1777,17 +1724,16 @@
:global MAX;
:set FileName [ $CleanFilePath $FileName ];
:local Delay ([ $MAX [ $EitherOr $WaitTime 2s ] 100ms ] / 9);
:local I 1;
:local Delay ([ $MAX [ $EitherOr $WaitTime 2s ] 100ms ] / 10);
:do {
:retry {
:if ([ :len [ /file/find where name=$FileName ] ] = 0) do={
:error false;
}
} delay=$Delay max=10;
} on-error={
:while ([ :len [ /file/find where name=$FileName ] ] = 0) do={
:if ($I >= 10) do={
:return false;
}
:delay $Delay;
:set I ($I + 1);
}
:while ([ :len [ /file/find where name=$FileName ] ] > 0) do={
:do {

View file

@ -40,11 +40,9 @@
:global EitherOr;
:global EMailGenerateFrom;
:global FileExists;
:global IsDNSResolving;
:global IsTimeSync;
:global LogPrint;
:global RmFile;
:local AllDone true;
:local QueueLen [ :len $EmailQueue ];
@ -95,7 +93,7 @@
:onerror Err {
:local Attach ({});
:foreach File in=[ :toarray [ $EitherOr ($Message->"attach") "" ] ] do={
:if ([ $FileExists $File ] = true) do={
:if ([ :len [ /file/find where name=$File ] ] = 1) do={
:set Attach ($Attach, $File);
} else={
$LogPrint warning $0 ("File '" . $File . "' does not exist, can not attach.");
@ -112,7 +110,7 @@
:set Wait false;
:if (($Message->"remove-attach") = true) do={
:foreach File in=$Attach do={
$RmFile $File;
/file/remove $File;
}
}
}

View file

@ -75,7 +75,6 @@
:local User [ :tostr $2 ];
:global EitherOr;
:global FileExists;
:global LogPrint;
:global ParseKeyValueStore;
:global SSHKeysImport;
@ -85,7 +84,8 @@
:return false;
}
:if ([ $FileExists $FileName ] = true) do={
:local File [ /file/find where name=$FileName ];
:if ([ :len $File ] = 0) do={
$LogPrint warning $0 ("File '" . $FileName . "' does not exist.");
:return false;
}

View file

@ -119,13 +119,11 @@
:local Data false;
:onerror Err {
:retry {
:set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \
http-header-field=({ "accept: application/dns-message" }) \
url=(($DohServer->"doh-url") . "?dns=" . [ :convert to=base64 ([ :rndstr length=2 ] . \
"\01\00" . "\00\01" . "\00\00" . "\00\00" . "\00\00" . "\09doh-check\05eworm\02de\00" . \
"\00\10" . "\00\01") ]) as-value ]->"data");
} delay=1s max=3;
} do={
$LogPrint warning $ScriptName ("Request to DoH server " . ($DohServer->"doh-url") . \
" failed: " . $Err);

View file

@ -31,25 +31,19 @@
:local Schedule do={
:local ScriptName [ :tostr $1 ];
:global PackagesUpdateDeferReboot;
:global GetRandomNumber;
:global IfThenElse;
:global LogPrint;
:global RebootForUpdate do={
/system/reboot;
}
:local Interval [ $IfThenElse ([ :totime $PackagesUpdateDeferReboot ] >= 1d) \
$PackagesUpdateDeferReboot 1d ];
:local StartTime [ :tostr [ :totime (10800 + [ $GetRandomNumber 7200 ]) ] ];
/system/scheduler/add name="_RebootForUpdate" start-time=$StartTime interval=$Interval \
/system/scheduler/add name="_RebootForUpdate" start-time=$StartTime interval=1d \
on-event=("/system/scheduler/remove \"_RebootForUpdate\"; " . \
":global RebootForUpdate; \$RebootForUpdate;");
$LogPrint info $ScriptName ("Scheduled reboot for update at " . $StartTime . \
" local time (" . [ /system/clock/get time-zone-name ] . ")" . \
[ $IfThenElse ($Interval > 1d) (" deferred by " . $Interval) ] . ".");
" local time (" . [ /system/clock/get time-zone-name ] . ").");
:return true;
}
@ -159,7 +153,7 @@
:error true;
}
} else={
:if ($PackagesUpdateDeferReboot = true || [ :totime $PackagesUpdateDeferReboot ] >= 1d) do={
:if ($PackagesUpdateDeferReboot = true) do={
$Schedule $ScriptName;
:set ExitOK true;
:error true;

View file

@ -30,7 +30,6 @@
:global CertificateAvailable;
:global EitherOr;
:global EscapeForRegEx;
:global FileExists;
:global GetRandom20CharAlNum;
:global IfThenElse;
:global LogPrint;
@ -101,7 +100,7 @@
$LogPrintVerbose debug $ScriptName ("Update " . $UpdateID . ": " . [ :serialize to=json $Update ]);
:local Message ($Update->"message");
:local IsAnyReply ([ :typeof ($Message->"reply_to_message") ] = "array");
:local IsReply ([ :typeof ($Message->"reply_to_message") ] = "string");
:local IsMyReply ($TelegramMessageIDs->[ :tostr ($Message->"reply_to_message"->"message_id") ]);
:if (($IsMyReply = 1 || $TelegramChatOffset->0 > 0 || $Uptime > 5m) && $UpdateID >= $TelegramChatOffset->2) do={
:local Trusted false;
@ -139,7 +138,7 @@
" from update " . $UpdateID . "!");
:set Done true;
}
:if ($Done = false && ($IsMyReply = 1 || ($IsAnyReply = false && \
:if ($Done = false && ($IsMyReply = 1 || ($IsReply = false && \
$TelegramChatActive = true)) && [ :len $Command ] > 0) do={
:if ([ $ValidateSyntax $Command ] = true) do={
:local State "";
@ -155,7 +154,7 @@
:if ([ $WaitForFile ($File . ".done") [ $EitherOr $TelegramChatRunTime 20s ] ] = false) do={
:set State ([ $SymbolForNotification "warning-sign" ] . "The command did not finish, still running in background.\n\n");
}
:if ([ $FileExists ($File . ".failed") ] = true) do={
:if ([ :len [ /file/find where name=($File . ".failed") ] ] > 0) do={
:set State ([ $SymbolForNotification "cross-mark" ] . "The command failed with an error!\n\n");
}
:local Content ([ /file/read chunk-size=32768 file=$File as-value ]->"data");