I ended up creating a class to handle this kind of shortcut:
// QT IFW seems to have a bug. If you use AllUsersStartMenuProgramsPath, it won't then set the icon for the shortcut.
// This wouldn't be a problem if the target exe had an icon builtin.
// Also, it ignores the StartMenuDir setting in config.xml.
// This class just does something like this:
// component.addElevatedOperation("CreateShortcut", `@TargetDir@/${itemName}`,
// `@AllUsersStartMenuProgramsPath@/${linkName}.lnk`,
// `iconPath=${iconPath}`);
class AllUserShortcut {
// Constructor method
constructor(companyName, productName) {
this.companyName = companyName;
this.productName = productName;
}
static createFolder(folderPath) {
// Create a folder if it doesn't exist and remove it on uninstall if it's empty.
var psCreateFolder = `
if (-Not (Test-Path -Path "${folderPath}")) {
New-Item -ItemType Directory -Path "${folderPath}"
}`;
var psDeleteEmptyFolder = `
if ((Test-Path -Path "${folderPath}") -and (Get-ChildItem -Path "${folderPath}").Count -eq 0) {
Remove-Item -Path "${folderPath}"
}`;
component.addElevatedOperation("Execute", [
"powershell.exe",
"-ExecutionPolicy", "Bypass",
"-Command", psCreateFolder
], "UNDOEXECUTE", [
"powershell.exe",
"-ExecutionPolicy", "Bypass",
"-Command", psDeleteEmptyFolder
]);
}
createShortcut(itemName, linkName, iconPath) {
console.log(`Creating shortcut: ${this.companyName}\\${this.productName}\\${linkName} -> @TargetDir\\${itemName}`);
var commonStartMenuDir = installer.environmentVariable("ALLUSERSPROFILE") + "\\Microsoft\\Windows\\Start Menu\\Programs";
// Create company folder.
var companyPath = commonStartMenuDir + `\\${this.companyName}`;
AllUserShortcut.createFolder(companyPath);
// Create product folder.
var productPath = companyPath + `\\${this.productName}`;
AllUserShortcut.createFolder(productPath);
// Use a PowerShell script to create the shortcut with the icon
var shortcutPath = productPath + `\\${linkName}.lnk`;
var targetPath = installer.value("TargetDir") + `\\${itemName}`;;
var psScriptContent = `
$WScriptShell = New-Object -ComObject WScript.Shell
$Shortcut = $WScriptShell.CreateShortcut("${shortcutPath}")
$Shortcut.TargetPath = "${targetPath}"`
if (iconPath) {
psScriptContent += `
$Shortcut.IconLocation = "${iconPath}"`
}
psScriptContent += `
$Shortcut.Save()
`;
component.addElevatedOperation("Execute", [
"powershell.exe",
"-ExecutionPolicy", "Bypass",
"-Command", psScriptContent
], "UNDOEXECUTE", [
"powershell.exe",
"-ExecutionPolicy", "Bypass",
"-Command", `Remove-Item -Path "${shortcutPath}" -Force`
]);
}
}