Specifically for Unity, you're correct that AssetBundles cannot update script code. You can use bundles for updating data, but any actual code changes will require a new version of the main game .exe to be installed. This can be an even bigger problem on non-desktop platforms, as releasing a new executable version on platforms like iOS can take a lot of time waiting for approval and release.
For that reason, it is often highly beneficial to make your game as data-driven as possible. The less you have to update code, the better. Prefer configuration in assets that you can put into bundles to drive a generic codebase, rather than having to patch the code itself.
The rest of this answer is not Unity-specific but tailored more to the specific ask of auto-updating games on Windows. :)
Auto-updating applications is a relatively non-trivial operation. Dealing with permissions correctly is non-obvious, actually writing all the server infrastructure to distribute updates is tricky, and auto-updating requires some tricks.
Permissions makes it difficult for an updater on Windows to install updates to C:\Program Files\
without administrator privileges, which your game itself won't have. Some apps thus install to %LOCALAPPDATA%
instead, or have to rely on an external updater utility that does have administrator privileges. Some users will refuse to play your game if it does require administrator privileges (even for updates) because they don't trust random game developers to get this stuff right (without opening their systems up to massive security holes).
Auto-updating in Windows is also tricky because you cannot (easily) replace a running binary or DLL. An updater effectively needs to either spawn an installer binary and exit itself (and let the installer do all the Windows update work), or it has to run itself from a temporary location so that it can freely modify its installation location.
A tool like NSIS or InnoSetup can be used to create the installers, and I believe both of those make it easy to create installers that install to a location in %LOCALAPPDATA%
.
However, if you add in the detection of updates, the download of new installers, verification (to make sure your update CDN isn't being hijacked!), and so on and you've got a lot of work on your hands.
By far, your best bet is to distribute on existing store platform. Windows Store, Steam, GOG, Epic Games Store, or one of the bazillion others (including some popular in non-Western regions of the world; I'm not myself familiar with those markets, though, so I don't know all the offerings).
Using an established store lets all the heavy-lifting of update distribution be handled by someone else. It lets the store handle detecting and installing updates. It absolves you of needing players' trust as the store app is the only software that requires administrator privileges. There's of course also the benefits in terms of exposure, sales, community, and so on, but with the downside of incurring some fiscal cost.
That said, there are some third-party tools that could help out. Squirrel for example is a .NET suite for creating auto-updating application (using Nuget, iirc, though it claims it works for native C++ applications, too), for example. It includes logic for multiple platforms (Windows, Mac, iOS) and includes a server-side component you'll have to host and configure. It may not be the right tool for this job, but it illustrates the necessary code for addressing the technical challenges I outlined above.
TL;DR: avoid requiring code changes as much as you possibly can; use an existing store or distribution channel to handle updates when you must; and be wary of the great complexity of auto-updates if you still want to roll your own.