The way I did this at my last job was to force an update check at the launch of the application. The app was an internal WinForm 1.1 app, with a webservice backend, since the users were not all in the same location. Rather than politely asking if the users wanted to update, we actually could require that they always work in the latest release.

So, I created a separate launcher exe that checked a webservice for a list of current files: if the list of files on the server differed from that on the client (determined by the hash of each file), then it'd download the necessary files, but only those. This had several benefits: We didn't need an installer for anything other than the launcher. If we only updated a single dll, then that was the only dll that needed to be sent to the clients. And should a user be boneheaded and delete an assembly, the application would heal itself.

To speed things up we compressed and decompressed the dlls (through the same web service compression utility we used for all webservice calls over a certain size).

The whole thing worked exceptionally well and was by far one of the more enjoyable tasks on the whole project (along with the custom web service compression task)...