This is the script I wrote that creates an uninstall.cmd. It runs as a custom action during installation.
var fso, ts;
var ForWriting= 2;
fso = new ActiveXObject("Scripting.FileSystemObject");
var parameters = Session.Property("CustomActionData").split("|");
var targetDir = parameters[0];
var productCode = parameters[1];
ts = fso.OpenTextFile(targetDir + "uninstall.cmd", ForWriting, true);
ts.WriteLine("@echo off");
ts.WriteLine("goto START");
ts.WriteLine("=======================================================");
ts.WriteBlankLines(1);
ts.WriteLine(" Uninstall.cmd");
ts.WriteBlankLines(1);
ts.WriteLine("=======================================================");
ts.WriteBlankLines(1);
ts.WriteLine(":START");
ts.WriteLine("@REM The uuid is the 'ProductCode' in the Visual Studio setup project");
ts.WriteLine("%windir%\\system32\\msiexec /x " + productCode);
ts.WriteBlankLines(1);
ts.Close();
The result is a cmd file that always has the current ProductCode in it.
The downside of this is that ?the script that creates the uninstall.cmd remains in the installation directory. Not a huge problem but I don't like the rubbish in the install directory. I haven't yet tried to make the "createInstaller.js" self-deleting. That might work.
EDIT: yes, making the createInstaller.js self-deleting works fine.
I'm going to accept my own answer!