In a large project that had about 30 separate modules, I built a small "updater" application which was a tiny application consisting of a Windows form, and about 3 code files. I deployed this to clients using .NET's ClickOnce technology. Whenever I had to "update the updater" (which was once in 2 years) I just pushed a new ClickOnce manifest out and clients got it the next time they logged in.
The updater, when ran, downloaded an XML manifest from the deployment server which contained a list of the binaries and libraries that made up the full application, along with the date/time and sizes of each binary file. It then compared those files with the local filesystem to determine which needed updating, and pulled down the ones it needed.
Afterwards, it then compared the list of the files on the manifest with the local filesystem, removed any files that shouldn't have been there, and executed the main application's executable.
This approach worked great for us, as I also used the same logic in the main application to update in the background, so the users could just restart the application when advised a new update had been installed.