diff --git a/CHANGELOG.md b/CHANGELOG.md index ce9c326..587db3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ## [Unreleased] +## [0.6.1] - 2026-08-30 + +### Changed + +- Windows setup is per-machine: install path `C:\Program Files\MaksIT\Cluster Console`, Start Menu folder **MaksIT**, shortcuts named **Cluster Console**. The bootstrapper install-folder box expands Program Files instead of showing a raw `[ProgramFiles6432Folder]` token. + ## [0.6.0] - 2026-08-30 ### Fixed diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 9aa0312..110de5f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,7 +3,7 @@ latest enable enable - 0.6.0 + 0.6.1 MaksIT.ClusterConsole MaksIT.ClusterConsole diff --git a/utils/engines/release/scriptSettings.json b/utils/engines/release/scriptSettings.json index 8415fab..9a9f6d1 100644 --- a/utils/engines/release/scriptSettings.json +++ b/utils/engines/release/scriptSettings.json @@ -56,10 +56,11 @@ "enabled": true, "appName": "MaksIT Cluster Console", "manufacturer": "MaksIT", + "installFolderName": "Cluster Console", "upgradeCode": "3e45e88a-6524-49cc-88e9-d522d1261b3a", "executableName": "MaksIT.ClusterConsole.UI.exe", "runtimeIdentifier": "win-x64", - "installScope": "perUser", + "installScope": "perMachine", "iconPath": "..\\..\\..\\src\\MaksIT.ClusterConsole.UI\\Assets\\icon.ico", "exeNamePattern": "maksit-cluster-console-{version}.exe" }, diff --git a/utils/plugins/Desktop/DesktopPackSupport.psm1 b/utils/plugins/Desktop/DesktopPackSupport.psm1 index 9a6b90f..c96f30b 100644 --- a/utils/plugins/Desktop/DesktopPackSupport.psm1 +++ b/utils/plugins/Desktop/DesktopPackSupport.psm1 @@ -27,6 +27,34 @@ function ConvertTo-WixIdentifier { return ($Prefix + $hash.Substring(0, 16)) } +function Get-DesktopInstallFolderName { + param( + [Parameter(Mandatory = $true)] + [string]$AppName, + + [Parameter(Mandatory = $false)] + [string]$Manufacturer, + + [Parameter(Mandatory = $false)] + [string]$InstallFolderName + ) + + if (-not [string]::IsNullOrWhiteSpace($InstallFolderName)) { + return $InstallFolderName.Trim() + } + + $name = $AppName.Trim() + $mfr = if ([string]::IsNullOrWhiteSpace($Manufacturer)) { '' } else { $Manufacturer.Trim() } + if ($mfr.Length -gt 0 -and $name.StartsWith($mfr, [System.StringComparison]::OrdinalIgnoreCase)) { + $rest = $name.Substring($mfr.Length).TrimStart([char[]]@(' ', '-', '.')) + if (-not [string]::IsNullOrWhiteSpace($rest)) { + return $rest + } + } + + return $name +} + function Get-MsiProductVersion { param( [Parameter(Mandatory = $true)] @@ -215,7 +243,10 @@ function New-WixPackageXml { [string]$ExecutablePath, [Parameter(Mandatory = $false)] - [string]$InstallScope = 'perUser', + [string]$InstallScope = 'perMachine', + + [Parameter(Mandatory = $false)] + [string]$InstallFolderName, [Parameter(Mandatory = $false)] [string]$IconPath @@ -260,27 +291,37 @@ function New-WixPackageXml { $null = $desktopProp.SetAttribute('Value', '0') $null = $package.AppendChild($desktopProp) + $productFolder = Get-DesktopInstallFolderName ` + -AppName $AppName ` + -Manufacturer $Manufacturer ` + -InstallFolderName $InstallFolderName + $stdLocal = $xml.CreateElement('StandardDirectory', $ns) $rootFolderId = if ($scope -eq 'perMachine') { 'ProgramFiles6432Folder' } else { 'LocalAppDataFolder' } $null = $stdLocal.SetAttribute('Id', $rootFolderId) $null = $package.AppendChild($stdLocal) - $programs = $xml.CreateElement('Directory', $ns) - $null = $programs.SetAttribute('Id', 'LocalProgramsFolder') - $null = $programs.SetAttribute('Name', 'Programs') - $null = $stdLocal.AppendChild($programs) + $installParent = $stdLocal + if (-not [string]::IsNullOrWhiteSpace($Manufacturer)) { + $mfrDir = $xml.CreateElement('Directory', $ns) + $null = $mfrDir.SetAttribute('Id', 'ManufacturerFolder') + $null = $mfrDir.SetAttribute('Name', $Manufacturer) + $null = $stdLocal.AppendChild($mfrDir) + $installParent = $mfrDir + } $installFolder = $xml.CreateElement('Directory', $ns) $null = $installFolder.SetAttribute('Id', 'INSTALLFOLDER') - $null = $installFolder.SetAttribute('Name', $AppName) - $null = $programs.AppendChild($installFolder) + $null = $installFolder.SetAttribute('Name', $productFolder) + $null = $installParent.AppendChild($installFolder) $stdMenu = $xml.CreateElement('StandardDirectory', $ns) $null = $stdMenu.SetAttribute('Id', 'ProgramMenuFolder') $null = $package.AppendChild($stdMenu) $menuDir = $xml.CreateElement('Directory', $ns) $null = $menuDir.SetAttribute('Id', 'AppShortcutFolder') - $null = $menuDir.SetAttribute('Name', $AppName) + $menuName = if ([string]::IsNullOrWhiteSpace($Manufacturer)) { $productFolder } else { $Manufacturer } + $null = $menuDir.SetAttribute('Name', $menuName) $null = $stdMenu.AppendChild($menuDir) $stdDesktop = $xml.CreateElement('StandardDirectory', $ns) @@ -344,7 +385,7 @@ function New-WixPackageXml { $null = $shortcut.SetAttribute('Guid', '*') $shortcutNode = $xml.CreateElement('Shortcut', $ns) $null = $shortcutNode.SetAttribute('Id', 'AppStartMenuShortcut') - $null = $shortcutNode.SetAttribute('Name', $AppName) + $null = $shortcutNode.SetAttribute('Name', $productFolder) $null = $shortcutNode.SetAttribute('Target', "[INSTALLFOLDER]$exeName") $null = $shortcutNode.SetAttribute('WorkingDirectory', 'INSTALLFOLDER') if (-not [string]::IsNullOrWhiteSpace($IconPath) -and (Test-Path -LiteralPath $IconPath -PathType Leaf)) { @@ -373,7 +414,7 @@ function New-WixPackageXml { $null = $desktop.SetAttribute('Condition', 'INSTALLDESKTOPSHORTCUT = 1') $desktopShortcut = $xml.CreateElement('Shortcut', $ns) $null = $desktopShortcut.SetAttribute('Id', 'AppDesktopShortcut') - $null = $desktopShortcut.SetAttribute('Name', $AppName) + $null = $desktopShortcut.SetAttribute('Name', $productFolder) $null = $desktopShortcut.SetAttribute('Target', "[INSTALLFOLDER]$exeName") $null = $desktopShortcut.SetAttribute('WorkingDirectory', 'INSTALLFOLDER') if (-not [string]::IsNullOrWhiteSpace($IconPath) -and (Test-Path -LiteralPath $IconPath -PathType Leaf)) { @@ -846,13 +887,20 @@ function New-WixBundleXml { [string]$ThemePath, [Parameter(Mandatory = $false)] - [string]$InstallScope = 'perUser' + [string]$InstallScope = 'perMachine', + + [Parameter(Mandatory = $false)] + [string]$InstallFolderName ) $escapedName = [System.Security.SecurityElement]::Escape($AppName) $escapedMfr = [System.Security.SecurityElement]::Escape($Manufacturer) $escapedMsi = [System.Security.SecurityElement]::Escape($MsiPath) $bundleUpgrade = Get-DerivedBundleUpgradeCode -UpgradeCode $UpgradeCode + $productFolder = Get-DesktopInstallFolderName ` + -AppName $AppName ` + -Manufacturer $Manufacturer ` + -InstallFolderName $InstallFolderName $iconAttr = '' if (-not [string]::IsNullOrWhiteSpace($IconPath)) { $iconAttr = " IconSourceFile=`"$([System.Security.SecurityElement]::Escape($IconPath))`"" @@ -874,20 +922,33 @@ function New-WixBundleXml { $logoAttrs += " ThemeFile=`"$([System.Security.SecurityElement]::Escape($ThemePath))`"" } - $folderRoot = if ($InstallScope -eq 'perMachine') { - '[ProgramFiles64Folder]Programs\' + # Type=formatted so WixStdBA expands well-known folders in the InstallFolder edit box. + # Type=string shows the raw token, e.g. [ProgramFiles6432Folder]MaksIT\Cluster Console. + # Burn CSIDL folders already end with a backslash, so do not insert another one. + # Layout is {ProgramFiles|LocalAppData}\{Manufacturer}\{product} — product folder is the + # internal name (appName with manufacturer prefix stripped, or installFolderName). + $folderRoot = if ($InstallScope -eq 'perUser') { + '[LocalAppDataFolder]' } else { - '[LocalAppDataFolder]Programs\' + '[ProgramFiles6432Folder]' } - $escapedFolder = [System.Security.SecurityElement]::Escape($folderRoot + $AppName) + $folderPath = if ([string]::IsNullOrWhiteSpace($Manufacturer)) { + $folderRoot + $productFolder + } + else { + $folderRoot + $Manufacturer + '\' + $productFolder + } + $escapedFolder = [System.Security.SecurityElement]::Escape($folderPath) + # WiX v7 Bundle has no Scope attribute (WIX0004). MSI Package/@Scope plus + # InstallFolder tokens decide per-machine vs per-user; Burn infers bundle scope. return @" - + @@ -906,6 +967,7 @@ function New-WixBundleXml { Export-ModuleMember -Function ` ConvertTo-WixIdentifier, ` Get-MsiProductVersion, ` + Get-DesktopInstallFolderName, ` Get-PluginPropertyValue, ` Resolve-DesktopPublishDirectory, ` Resolve-DesktopExecutablePath, ` diff --git a/utils/plugins/Desktop/WindowsInstaller.psm1 b/utils/plugins/Desktop/WindowsInstaller.psm1 index 77020f9..9c1bd34 100644 --- a/utils/plugins/Desktop/WindowsInstaller.psm1 +++ b/utils/plugins/Desktop/WindowsInstaller.psm1 @@ -102,7 +102,8 @@ function Invoke-Plugin { $upgradeCode = [guid]$upgradeCodeRaw $manufacturer = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'manufacturer' -Default 'MaksIT') $runtimeIdentifier = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'runtimeIdentifier' -Default 'win-x64') - $installScope = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'installScope' -Default 'perUser') + $installScope = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'installScope' -Default 'perMachine') + $installFolderName = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'installFolderName') $executableName = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'executableName') $publishDirSetting = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'publishDir') $iconSetting = [string](Get-PluginPropertyValue -PluginSettings $pluginSettings -Name 'iconPath') @@ -217,6 +218,7 @@ function Invoke-Plugin { -PublishDirectory $publishDirectory ` -ExecutablePath $executablePath ` -InstallScope $installScope ` + -InstallFolderName $installFolderName ` -IconPath $iconPath $xml.Save($wxsPath) @@ -262,7 +264,8 @@ function Invoke-Plugin { -LogoPath $logoPath ` -LogoSidePath $logoSidePath ` -ThemePath $themePath ` - -InstallScope $installScope + -InstallScope $installScope ` + -InstallFolderName $installFolderName [System.IO.File]::WriteAllText($bundleWxsPath, $bundleXml, [System.Text.UTF8Encoding]::new($false)) if (Test-Path -LiteralPath $exePath -PathType Leaf) {