Reading the FTP Automation You Inherited
Every administrator eventually inherits one: a scheduled task that runs a batch file that runs an FTP script written by someone who left the company years ago. It moves files every night — possibly important ones, possibly to a bank — and the only documentation is a comment that says DO NOT TOUCH. You cannot leave it alone forever (it has a plaintext password in it, and someday it will break), and you cannot casually rewrite it either (something downstream depends on exactly what it does, including its quirks).
This article is the field manual for that situation. You will learn where these jobs hide, how to read the two classic script dialects line by line, where the embedded credentials live, how to document what a job actually does before touching anything, and how to modernize in an order that never breaks the business. It is part of our The FTP Protocol series, and it leans on skills from the rest of the series — reading session logs, understanding accounts — exactly the way real archaeology leans on real tools.
The Prime Directive: Document Before You Touch
Inherited jobs deserve two attitudes at once. Respect, because they encode business knowledge nowhere else recorded — the fact that the partner's server wants files in /inbound2, not /inbound, is written only there. And suspicion, because they were built in an era of different standards: credentials in plain text, no error handling, no alerting, and a transport that ships passwords readable across the network.
Both attitudes point to the same rule: nothing gets changed, "cleaned up," or migrated until the job is documented — trigger, direction, endpoints, account, files, and consumers. Half of all inherited-automation outages are self-inflicted, caused by an improvement made before understanding. The documentation pass below usually takes under an hour per job, which is cheaper than any outage it prevents.
Remember: an inherited job is production code with no tests and no author. Document first, change second. The one exception: if you find credentials exposed somewhere genuinely public, contain that immediately — but contain it by fixing exposure, not by rewriting the job.
Where Inherited Jobs Hide
Before you can read them, you have to find them. The usual habitats:
- Windows Task Scheduler — the top habitat. Export the task list and look for anything invoking
.bat,.cmd, orftp.exedirectly. Note which account each task runs as. - cron on Unix-family systems — check system crontabs and each service account's personal crontab; the interesting jobs often belong to accounts nobody logs into anymore.
- Inside applications — ERP export steps, backup-tool "post-job commands," report generators with a "send to FTP" checkbox. These are automation too; they just hide inside other software's config.
- The unofficial machine — the PC under a desk that "has to stay on" is load-bearing infrastructure in disguise. Whatever it is doing, it is doing it with a scheduled task.
- File search — sweep likely hosts for
*.batand*.cmdcontaining "ftp", for-s:(theftp.exescript flag), and for.netrcfiles. Each hit is either a live job, a dead job, or a copy of a live job — all worth recording.
This hunt is really one corner of a larger discipline — inventorying every file flow your organization runs — which our file transfer fundamentals series treats properly. For now, a simple list is enough: job, host, trigger, script path.
Anatomy of a Windows Batch Job
The classic Windows specimen is a pair of files: a batch wrapper and an FTP command script. Here is a representative wrapper — the annotations after the # marks are ours:
@echo off rem nightly pull from bank -- DO NOT TOUCH (J.M.) cd /d D:\jobs\bankfeed # everything is relative to this folder ftp -i -s:getfiles.ftp bank.example.com # -s: feeds ftp.exe a canned command file copy /Y in\*.csv \\finance01\import\ # relay the files onward to a share del in\*.csv # ...then tidy up. No checks anywhere.
And the command file it feeds, getfiles.ftp — the part that actually speaks FTP:
user feeduser feedpass!7 # credentials, in plain text, in a file binary # at least they set binary mode cd /outgoing # remote folder on the bank's server lcd in # local folder under D:\jobs\bankfeed mget *.csv # -i on the wrapper suppresses prompts bye
Now the traps, in descending order of danger:
ftp.execannot fail. It exits with a success code even when login is refused or every transfer fails, so the wrapper'scopyanddelrun regardless, and Task Scheduler reports a green run. This job has no way to tell anyone it is broken. If it has ever failed, it failed silently.ftp.exeis active-mode-only. It cannot use passive mode at all, which means this job works only while the network happens to permit active-mode callbacks. A firewall change anywhere on the path kills it — the mechanics are in active vs passive FTP explained. When an old batch job "suddenly stopped working after the firewall upgrade," this is why.- The credential sits in a text file, readable by whoever can read the folder, backed up by whatever backs up the folder, and copied along with every copy of the job anyone ever made.
- The relay compounds the silence. If
mgetfetched nothing,copyfinds nothing, the consumer receives nothing, and the first human to notice is in finance, days later.
Anatomy of a Unix Heredoc Job
The Unix-family equivalent wraps the client in a shell script and feeds it commands through a heredoc — an inline block of text redirected to the program's input:
#!/bin/sh # nightly push to fulfillment partner cd /var/exports ftp -inv ftp.partner.example <<EOF # -i no prompts, -n no auto-login, -v verbose user pushuser feedpass7 # -n exists so THIS line can supply credentials binary cd inbound put orders.csv # push today's export bye EOF
Same species, different plumage: credentials in the script body (or sometimes in a .netrc file in the home directory, which at least supports strict file permissions), no checking of whether put succeeded, and an exit status that reflects whether ftp ran, not whether it worked. If the cron job's output goes nowhere — a common configuration — failures are not just silent but invisible. When you read one of these, translate each line back to the protocol it drives; the vocabulary is exactly the one in FTP commands and reply codes, and the verbose -v flag means old log files, where they exist, contain readable reply codes worth mining.
Other Dialects You May Meet
Not every inherited job speaks batch or heredoc. Three more dialects turn up regularly, and the reading method is the same for all of them — find the connection line, the credential, and the transfer verbs:
- Script files for GUI clients. Several graphical FTP clients have scripting modes, driven by a text file of their own commands. The tell is an unfamiliar command vocabulary plus an
openorsessionline containing a hostname — and, typically, the password on the same line. - PowerShell and VBScript jobs. Windows scripts that build FTP requests through system libraries. Search them for
ftp://URLs and for variables named anything like$password; the URL form sometimes embeds credentials directly asftp://user:password@host, which is a credential exposure and a migration note in one line. - Vendor-tool steps. Backup suites, report generators, and ERP systems with built-in "upload results" steps store host and credentials in their own configuration. They rarely appear in any script sweep — you find them by asking what produces each file that arrives on your server.
The Credential Hunt
Every inherited FTP job stores a password somewhere, because unattended login requires one. Your job is to find every copy. The usual locations, roughly in order of discovery frequency:
- The command file or script body itself, as above.
.netrcfiles in service-account home directories.- Saved site entries in graphical clients on admin workstations — the "temporary" test that became the job.
- Application config files and INI files for software with built-in FTP steps.
- Comments and README files:
rem password is feedpass7 if script eaten. - Environment variables and scheduler job definitions — some jobs read the password from a variable set machine-wide, which relocates the exposure without reducing it.
- Copies of all of the above, in backup folders, in
old\directories, and on the previous admin's archived machine.
Handling discipline when you find one: record the location in your inventory, never the password itself — no credentials in tickets, chat, or the documentation you are writing. Check the blast radius: is this password unique to this FTP account, or is it — worst case — also an operating-system or domain password? (The difference between those two worlds is exactly why virtual, jailed FTP accounts exist.) Plan rotation for after the job is documented and stabilized: rotating a credential before you know every place it is stored is how Tuesday's cleanup becomes Wednesday's outage.
The Documentation Pass
Now convert what you have learned into the record that makes every later decision easy. One block per job, kept somewhere the whole team can find:
JOB: bankfeed-nightly Found at: Task Scheduler "Nightly bank pull" -> D:\jobs\bankfeed\nightly.bat Trigger: daily 02:15, runs as local account "batchsvc" Direction: pull from partner, then relay to \\finance01\import Remote: bank.example.com : 21, plain FTP, active mode (ftp.exe) Account: feeduser (credential location: getfiles.ftp line 1 -- not recorded here) Files: *.csv from /outgoing, deleted locally after relay Success: NOT DETECTED -- ftp.exe always exits 0; no logging, no alerting Consumer: finance import service reads the share at 04:00 Owner: unassigned -- proposed: finance-ops Quirks: partner's server requires active mode? unverified -- test in lab
Two safe ways to fill the gaps without touching production. First, watch a real run: read the job's own output or the server-side log for one scheduled execution, changing nothing. Second — better — replay the script in a lab: point a copy at a disposable FTP server that mirrors the account's folder shape, and capture what it actually sends. Our FTP lab guide exists for precisely this; a mystery script becomes an open book the first time you read its session on the wire.
Assessing Risk Without Panic
With documentation in hand, score each job on four axes — not to generate fear, but to order the work:
- Credential exposure: who can read the places the password lives, and is it reused anywhere that matters?
- Transport exposure: what network does the plaintext session cross — one isolated VLAN, or the internet?
- Silence: if the job failed tonight, who would know, and when? "The consumer, eventually" is the honest answer for most.
- Fragility: active-mode dependence, no retry, parsing of listing output, hard-coded paths — how many quiet assumptions is it balanced on?
Jobs that score badly on the first two axes get containment moves now: scope the FTP account down to the one folder it needs, make its password unique, restrict who can read the script folder. Jobs that score badly on the last two get stabilization: at minimum, wrap them with logging and an alert on the things you can detect (file absent, file zero bytes, consumer folder empty at deadline). Neither move changes what the job does — that is what makes them safe to do early.
Modernizing Deliberately
One more reading skill before the rebuild: learn to spot which quirks are load-bearing. Downstream systems adapt to whatever a job actually does — the consumer may depend on the exact filename casing, on files arriving after a certain time, or even on a bug (a duplicate upload the consumer silently deduplicates, say). Your documentation should record these observed behaviors as requirements-until-proven-otherwise. The most common modernization outage is not a failed transfer; it is a better transfer — faster, earlier, correctly named — that the downstream process was never built to expect.
When a job's turn comes, modernize in this order, one rung at a time, comparing results against your documentation at each step:
- Stabilize — logging and alerting around the existing job (done above).
- Re-platform the job — move the transfer into a tool built for unattended work, keeping the flow identical: same server, same account, same files. The difference is that a purpose-built scheduler checks reply codes, retries transient failures, and raises errors a human hears about. On Windows, this is what Sysax FTP Automation is for: scheduled, scripted transfers with folder monitoring, retry and error handling, and pre/post-processing steps that replace the
copy-and-delchoreography batch files do badly. - Rotate the credentials — now that they live in one managed place instead of five text files, retire the old password everywhere your hunt found it.
- Upgrade the transport — with the job re-platformed, switching plain FTP to FTPS or SFTP is a connection-setting change rather than a rewrite, coordinated with the far end. The choosing is covered in our SFTP vs FTPS vs FTP series.
- Retire the old artifacts — the batch files, command files, and their backup copies, which still contain the old credentials. Archive one copy in a restricted location for history; delete the rest.
The pattern-by-pattern translation, for reference:
| Legacy pattern | Its weakness | Modern practice |
|---|---|---|
| Password in script or -s: file | Readable, backed up, copied everywhere | Credentials stored once, in the transfer tool's profile |
| ftp.exe in a batch wrapper | Exits 0 on failure; active mode only | Reply-code-aware engine with real success/failure status |
| No retry, no alerting | One network blip = silent missing files | Automatic retry on transient errors; alerts on hard ones |
| Fixed-time schedule guessing at readiness | Races the producer; grabs partial files | Folder monitoring — act when the file actually arrives |
| Plain FTP across shared networks | Credentials and data readable in transit | FTPS or SFTP, switched at the connection profile |
The Archaeologist's Summary
Inherited FTP automation is neither shameful nor safe — it is undocumented production code, and the cure for undocumented is documentation, not bravado. Find every job, read it line by line with the protocol vocabulary, hunt down every stored credential, write the one-block record, stabilize the silent failures, and only then modernize, one rung at a time, with the old and new outputs compared against each other. Done in that order, the scary folder of batch files becomes a managed set of transfer jobs — and nothing downstream ever notices the transition, which is the whole definition of success.
For the supporting skills: commands and reply codes teaches the language these scripts speak, the failure modes field guide catalogs what happens when they break, and the FTP lab gives you the safe place to run anything you don't yet trust.
Frequently Asked Questions
Why does our old FTP batch job report success even when it fails?
Is it safe to just run an inherited script to see what it does?
I found a plaintext FTP password in a script. What do I do first?
Should I rewrite inherited jobs as new scripts or move them into a transfer tool?
How do I find all the scheduled FTP jobs in my environment?
The admin who wrote these jobs is still reachable. What should I ask?
From the Sysax team: we build secure file transfer software for Windows — Sysax Multi Server, an FTP, FTPS, SFTP, and HTTPS server, and Sysax FTP Automation for scheduled, scripted transfers. Free trials are on the download page.
