With GradualTest, you can use it to control an engine to analyze a set of positions. The only version available through web.archive is 2.4 (a compiled version and program source) but exists version 2.6. The author is Gunnar Malin, available in GitHub.
Its syntax is:
/a - Init time in second, default=2.
/c - Command to reset hash. Defaults to 'new'.
/e - Method to set up the board. Values: edit, cbedit or setboard.
Default=setboard. (cbedit is used if the engine have implemented
Chessbase's method of edit.)
/i - Input file to be tested.
/o - Output file.
/s - Init string for the engine. Defaults to
'xboard\nhard\neasy\nnew\nlevel 1 1440 0\npost\n'.
/t - Time to use at each position.
/w - File to write positions with move not found.
/g - File to write positions with only good moves
/u - Use the UCI protocol.
It write also the following epd fields:
acd - Depth of the search for the pv line.
acn - Number of node serched for the pv line.
acs - Seconds searched for the pv line.
ce - Centipawn evaluation of the pv line.
pm - The best move from the engine.
pv - The last pv line (pv=Predicted Variation).
With crafty (a winboard engine) I configured it in 'crafty.rc', sd=21 for instance, but with enough time to reach a depth of 21 ply. When the time to reach this depth was inferior than the time provided with GradualTest, the program somehow knows that position analyze has finished and proceded with the following position.