My rule of thumb is that when I have to do something for the third time, it's time to either write a little script to automate it, or rethink my approach.
I'm not making a full-blown "tool" at this point, just a little script (usually bash or python; perl would work too, or even PHP) that automates what I did manually before. It's basically an application of the DRY principle (or the Single Source Of Truth principle, which is in essence the same thing) - if you have to change two source files in tandem, there has to be some common truth that they share, and that truth has to be factored out and stored in one central place. It's great if you can solve this internally by refactoring, but sometimes, this isn't feasible, and that's where the custom scripts come in.
Then later, the script may or may not evolve into a full-blown tool, but I usually start out with a very specific script with lots of things hard-coded into it.
I hate grunt work with a passion, but I also strongly believe that it is a sign of bad or incorrect design. Being lazy is an important quality in a programmer, and it better be the kind where you go through great lengths to avoid repetitive work.
Sure, sometimes the balance is negative - you spend three hours refactoring your code or writing a script to save you one hour of repetitive work; but usually, the balance is positive, more so if you consider costs that aren't directly apparent: human failure (humans are really bad at repetitive work), smaller codebase, better maintainability due to reduced redundancy, better self-documentation, faster future development, cleaner code. So even if the balance appears negative right now, the codebase will grow further, and that tool you wrote to generate web forms for three data objects will still work when you have thirty data objects. In my experience, the balance is usually estimated in favor of grunt work, probably because repetitive tasks are easier to estimate and thus under-estimated, while refactoring, automating and abstracting are perceived as less predictable and more dangerous, and thus over-estimated. It usually turns out that automating isn't that hard after all.
And then there's the risk of doing it too late: it's easy to refactor three
brand new data object classes into shape and write a script that generates web forms for them, and once you've done that, it's easy to add 27 more classes that also work with your script. But it's close to impossible to write that script when you have reached a point where there are 30 data object classes, each with hand-written web forms, and without any consistency between them (a.k.a. "organic growth"). Maintaining those 30 classes with their forms is a nightmare of repetitive coding and semi-manual search-replace, changing common aspects takes thirty times as long as it should, but writing a script to solve the problem, which would have been a lunch break no-brainer when the project started out is now a frightful two-week project with the dreading prospect of a month-long aftermath consisting of fixing bugs, educating users, and possibly even giving up and reverting to the old codebase. Ironically, writing the 30-class mess took way longer than the clean solution would have, because you could have been riding the convenient script all that time. In my experience, automating repetitive work away too late is one of the major problems in long-running large software projects.