View Issue Details

IDProjectCategoryView StatusLast Update
0001728OpenMPTInstaller and Updatepublic2025-06-14 06:38
ReporterSaga Musix Assigned Tomanx  
PrioritynormalSeverityminorReproducibilityhave not tried
Status resolvedResolutionfixed 
Target VersionOpenMPT 1.33 / libopenmpt 0.9 (goals)Fixed in VersionOpenMPT 1.33 / libopenmpt 0.9 (goals) 
Summary0001728: Rewrite portable updater in another language
Description

According to https://winaero.com/vbscript-has-become-optional-component-in-windows-11/ it appears that the VBScript interpreter has become an optional component in Windows 11. From my understanding, it will still be installed by default, but it is possible for individual users to uninstall the interpreter, which would break the OpenMPT update mechanism for portable versions.

If it's true that it will still be installed by default on all Windows 11 installations, there is no urgency in this, but I think we should explore potential alternatives.

  • If JScript is not affected, this might be the most portable solution as it is supported by very old Windows versions
  • Otherwise PowerShell might be a viable alternative (though according to the screenshot in that article, it is also an optional component, though probably a less likely one to be uninstalled)
  • If none of these are viable, maybe a small companion exe could be used.
TagsNo tags attached.
Has the bug occurred in previous versions?
Tested code revision (in case you know it)

Activities

manx

manx

2025-06-13 15:58

administrator   ~0006393

Last edited: 2025-06-14 06:36

Only mildly tested in isolation for now.

The patch uses PowerShell instead of VBScript from Windows 11 onwards.

TODO:

  • verify which PowerShell version the script requires and what is available on the earliest Windows 11
  • check Wine compatibility
  • real-world test
update-portable-powershell-v1.patch (7,686 bytes)   
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 23480)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -937,7 +937,7 @@
 
 
 
-static const char updateScript[] = R"vbs(
+static const char updateScript_vbs[] = R"vbs(
 
 Wscript.Echo
 Wscript.Echo "OpenMPT portable Update"
@@ -1016,7 +1016,59 @@
 )vbs";
 
 
+static const char updateScript_ps1[] = R"ps1(
 
+param(
+	[String]$zip="",
+	[String]$subfolder="",
+	[String]$dst="",
+	[String]$restartbinary="")
+
+Write-Output ""
+Write-Output "OpenMPT portable Update"
+Write-Output "======================="
+
+Write-Output "[  0%] Waiting for OpenMPT to close..."
+Start-Sleep -Seconds 2
+
+Write-Output "[ 10%] Changing to temporary directory..."
+Set-Location -Path (Split-Path -Parent $MyInvocation.MyCommand.Definition)
+
+Write-Output "[ 20%] Decompressing update..."
+Expand-Archive -Path $zip -DestinationPath (Join-Path -Path (Resolve-Path -Path ".") -ChildPath "tmp") -Force
+
+Write-Output "[ 40%] Installing update..."
+if (($subfolder -eq "") -or ($subfolder -eq ".")) {
+	Copy-Item -Path (Join-Path -Path (Join-Path -Path (Resolve-Path -Path ".") -ChildPath "tmp") -ChildPath "*") -Destination $dst -Recurse -Force
+} else {
+	Copy-Item -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path (Resolve-Path -Path ".") -ChildPath "tmp") -ChildPath $subfolder) -ChildPath "*") -Destination $dst -Recurse -Force
+}
+
+Write-Output "[ 60%] Deleting temporary directory..."
+Remove-Item -Path (Join-Path -Path (Resolve-Path -Path ".") -ChildPath "tmp") -Recurse -Force
+
+Write-Output "[ 80%] Restarting OpenMPT..."
+Start-Process -FilePath (Join-Path -Path (Resolve-Path -Path $dst) -ChildPath $restartbinary)  -WorkingDirectory $dst
+
+Write-Output "[100%] Update successful!"
+Write-Output ""
+Start-Sleep -Seconds 1
+
+Write-Output "Closing update window in 5 seconds..."
+Start-Sleep -Seconds 1
+Write-Output "Closing update window in 4 seconds..."
+Start-Sleep -Seconds 1
+Write-Output "Closing update window in 3 seconds..."
+Start-Sleep -Seconds 1
+Write-Output "Closing update window in 2 seconds..."
+Start-Sleep -Seconds 1
+Write-Output "Closing update window in 1 seconds..."
+Start-Sleep -Seconds 1
+Write-Output "Closing update window..."
+
+)ps1";
+
+
 class CDoUpdate: public CProgressDialog
 {
 private:
@@ -1306,35 +1358,75 @@
 					}
 				} else if(download.type == U_("archive") && downloadinfo.autoupdate_archive)
 				{
-					try
+					if(mpt::osinfo::windows::Version::Current().IsAtLeast(mpt::osinfo::windows::Version::Win10, 22000))
 					{
-						mpt::IO::SafeOutputFile file(dirTempOpenMPTUpdates + P_("update.vbs"), std::ios::binary);
-						file.stream().imbue(std::locale::classic());
-						file.stream().exceptions(std::ios::failbit | std::ios::badbit);
-						mpt::IO::WriteRaw(file.stream(), mpt::as_span(std::string(updateScript)));
-					} catch(...)
+						try
+						{
+							mpt::IO::SafeOutputFile file(dirTempOpenMPTUpdates + P_("update.ps1"), std::ios::binary);
+							file.stream().imbue(std::locale::classic());
+							file.stream().exceptions(std::ios::failbit | std::ios::badbit);
+							mpt::IO::WriteRaw(file.stream(), mpt::as_span(std::string(updateScript_ps1)));
+						} catch(...)
+						{
+							throw Error(U_("Error creating update script."));
+						}
+						std::vector<mpt::ustring> arguments;
+						arguments.push_back(U_("-ExecutionPolicy"));
+						arguments.push_back(U_("Unrestricted"));
+						arguments.push_back(U_("\"") + (dirTempOpenMPTUpdates + P_("update.ps1")).ToUnicode() + U_("\""));
+						arguments.push_back(U_("-zip"));
+						arguments.push_back(U_("\"") + updateFilename.ToUnicode() + U_("\""));
+						arguments.push_back(U_("-subfolder"));
+						arguments.push_back(U_("\"") + (downloadinfo.autoupdate_archive->subfolder.empty() ? U_(".") : downloadinfo.autoupdate_archive->subfolder) + U_("\""));
+						arguments.push_back(U_("-dst"));
+						arguments.push_back(U_("\"") + theApp.GetInstallPath().WithoutTrailingSlash().ToUnicode() + U_("\""));
+						arguments.push_back(U_("-restartbinary"));
+						arguments.push_back(U_("\"") + downloadinfo.autoupdate_archive->restartbinary + U_("\""));
+						if(theApp.IsSourceTreeMode())
+						{
+							throw Warning(MPT_UFORMAT("Refusing to launch update '{} {}' when running from source tree.")(P_("cscript.exe"), mpt::join_format(arguments, U_(" "))));
+						}
+						if(reinterpret_cast<INT_PTR>(ShellExecute(NULL, NULL,
+							P_("powershell").AsNative().c_str(),
+							mpt::ToWin(mpt::join_format(arguments, U_(" "))).c_str(),
+							dirTempOpenMPTUpdates.AsNative().c_str(),
+							SW_SHOWDEFAULT)) < 32)
+						{
+							throw Error(U_("Error launching update."));
+						}
+						wantClose = true;
+					} else
 					{
-						throw Error(U_("Error creating update script."));
+						try
+						{
+							mpt::IO::SafeOutputFile file(dirTempOpenMPTUpdates + P_("update.vbs"), std::ios::binary);
+							file.stream().imbue(std::locale::classic());
+							file.stream().exceptions(std::ios::failbit | std::ios::badbit);
+							mpt::IO::WriteRaw(file.stream(), mpt::as_span(std::string(updateScript_vbs)));
+						} catch(...)
+						{
+							throw Error(U_("Error creating update script."));
+						}
+						std::vector<mpt::ustring> arguments;
+						arguments.push_back(U_("\"") + (dirTempOpenMPTUpdates + P_("update.vbs")).ToUnicode() + U_("\""));
+						arguments.push_back(U_("\"") + updateFilename.ToUnicode() + U_("\""));
+						arguments.push_back(U_("\"") + (downloadinfo.autoupdate_archive->subfolder.empty() ? U_(".") : downloadinfo.autoupdate_archive->subfolder) + U_("\""));
+						arguments.push_back(U_("\"") + theApp.GetInstallPath().WithoutTrailingSlash().ToUnicode() + U_("\""));
+						arguments.push_back(U_("\"") + downloadinfo.autoupdate_archive->restartbinary + U_("\""));
+						if(theApp.IsSourceTreeMode())
+						{
+							throw Warning(MPT_UFORMAT("Refusing to launch update '{} {}' when running from source tree.")(P_("cscript.exe"), mpt::join_format(arguments, U_(" "))));
+						}
+						if(reinterpret_cast<INT_PTR>(ShellExecute(NULL, NULL,
+							P_("cscript.exe").AsNative().c_str(),
+							mpt::ToWin(mpt::join_format(arguments, U_(" "))).c_str(),
+							dirTempOpenMPTUpdates.AsNative().c_str(),
+							SW_SHOWDEFAULT)) < 32)
+						{
+							throw Error(U_("Error launching update."));
+						}
+						wantClose = true;
 					}
-					std::vector<mpt::ustring> arguments;
-					arguments.push_back(U_("\"") + (dirTempOpenMPTUpdates + P_("update.vbs")).ToUnicode() + U_("\""));
-					arguments.push_back(U_("\"") + updateFilename.ToUnicode() + U_("\""));
-					arguments.push_back(U_("\"") + (downloadinfo.autoupdate_archive->subfolder.empty() ? U_(".") : downloadinfo.autoupdate_archive->subfolder) + U_("\""));
-					arguments.push_back(U_("\"") + theApp.GetInstallPath().WithoutTrailingSlash().ToUnicode() + U_("\""));
-					arguments.push_back(U_("\"") + downloadinfo.autoupdate_archive->restartbinary + U_("\""));
-					if(theApp.IsSourceTreeMode())
-					{
-						throw Warning(MPT_UFORMAT("Refusing to launch update '{} {}' when running from source tree.")(P_("cscript.exe"), mpt::join_format(arguments, U_(" "))));
-					}
-					if(reinterpret_cast<INT_PTR>(ShellExecute(NULL, NULL,
-						P_("cscript.exe").AsNative().c_str(),
-						mpt::ToWin(mpt::join_format(arguments, U_(" "))).c_str(),
-						dirTempOpenMPTUpdates.AsNative().c_str(),
-						SW_SHOWDEFAULT)) < 32)
-					{
-						throw Error(U_("Error launching update."));
-					}
-					wantClose = true;
 				} else
 				{
 					CTrackApp::OpenDirectory(dirTempOpenMPTUpdates);
manx

manx

2025-06-13 17:24

administrator   ~0006394

The version of PowerShell shipped by any Windows 11 is at least 5.1 (see https://learn.microsoft.com/en-us/powershell/scripting/install/powershell-support-lifecycle?view=powershell-7.5#windows-powershell-release-history), which is also the latest version that is shipped with Windows. This concludes that no further compatibility testing needs to be done on Windows 11.

manx

manx

2025-06-13 17:48

administrator   ~0006395

update-portable-powershell-v2.patch (8,827 bytes)   
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 23480)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -358,6 +358,7 @@
 	, UpdateIgnoreVersion(conf, UL_("Update"), UL_("IgnoreVersion"), _T(""))
 	, UpdateSkipSignatureVerificationUNSECURE(conf, UL_("Update"), UL_("SkipSignatureVerification"), false)
 	, UpdateSigningKeysRootAnchors(conf, UL_("Update"), UL_("SigningKeysRootAnchors"), CUpdateCheck::GetDefaultUpdateSigningKeysRootAnchors())
+	, UpdatePortableBackend(conf, UL_("Update"), UL_("PortableBackend"), 0)
 #endif // MPT_ENABLE_UPDATE
 	// Wine suppport
 	, WineSupportEnabled(conf, UL_("WineSupport"), UL_("Enabled"), false)
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 23480)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -1002,6 +1002,7 @@
 	Setting<CString> UpdateIgnoreVersion;
 	Setting<bool> UpdateSkipSignatureVerificationUNSECURE;
 	Setting<std::vector<mpt::ustring>> UpdateSigningKeysRootAnchors;
+	Setting<int32> UpdatePortableBackend;
 
 #endif // MPT_ENABLE_UPDATE
 
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 23480)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -937,7 +937,7 @@
 
 
 
-static const char updateScript[] = R"vbs(
+static const char updateScript_vbs[] = R"vbs(
 
 Wscript.Echo
 Wscript.Echo "OpenMPT portable Update"
@@ -1016,7 +1016,59 @@
 )vbs";
 
 
+static const char updateScript_ps1[] = R"ps1(
 
+param(
+	[String]$zip="",
+	[String]$subfolder="",
+	[String]$dst="",
+	[String]$restartbinary="")
+
+Write-Output ""
+Write-Output "OpenMPT portable Update"
+Write-Output "======================="
+
+Write-Output "[  0%] Waiting for OpenMPT to close..."
+Start-Sleep -Seconds 2
+
+Write-Output "[ 10%] Changing to temporary directory..."
+Set-Location -Path (Split-Path -Parent $MyInvocation.MyCommand.Definition)
+
+Write-Output "[ 20%] Decompressing update..."
+Expand-Archive -Path $zip -DestinationPath (Join-Path -Path (Resolve-Path -Path ".") -ChildPath "tmp") -Force
+
+Write-Output "[ 40%] Installing update..."
+if (($subfolder -eq "") -or ($subfolder -eq ".")) {
+	Copy-Item -Path (Join-Path -Path (Join-Path -Path (Resolve-Path -Path ".") -ChildPath "tmp") -ChildPath "*") -Destination $dst -Recurse -Force
+} else {
+	Copy-Item -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path (Resolve-Path -Path ".") -ChildPath "tmp") -ChildPath $subfolder) -ChildPath "*") -Destination $dst -Recurse -Force
+}
+
+Write-Output "[ 60%] Deleting temporary directory..."
+Remove-Item -Path (Join-Path -Path (Resolve-Path -Path ".") -ChildPath "tmp") -Recurse -Force
+
+Write-Output "[ 80%] Restarting OpenMPT..."
+Start-Process -FilePath (Join-Path -Path (Resolve-Path -Path $dst) -ChildPath $restartbinary)  -WorkingDirectory $dst
+
+Write-Output "[100%] Update successful!"
+Write-Output ""
+Start-Sleep -Seconds 1
+
+Write-Output "Closing update window in 5 seconds..."
+Start-Sleep -Seconds 1
+Write-Output "Closing update window in 4 seconds..."
+Start-Sleep -Seconds 1
+Write-Output "Closing update window in 3 seconds..."
+Start-Sleep -Seconds 1
+Write-Output "Closing update window in 2 seconds..."
+Start-Sleep -Seconds 1
+Write-Output "Closing update window in 1 seconds..."
+Start-Sleep -Seconds 1
+Write-Output "Closing update window..."
+
+)ps1";
+
+
 class CDoUpdate: public CProgressDialog
 {
 private:
@@ -1306,33 +1358,82 @@
 					}
 				} else if(download.type == U_("archive") && downloadinfo.autoupdate_archive)
 				{
-					try
+					bool usePowerShell = mpt::osinfo::windows::Version::Current().IsAtLeast(mpt::osinfo::windows::Version::Win10, 22000);
+					switch(TrackerSettings::Instance().UpdatePortableBackend)
 					{
-						mpt::IO::SafeOutputFile file(dirTempOpenMPTUpdates + P_("update.vbs"), std::ios::binary);
-						file.stream().imbue(std::locale::classic());
-						file.stream().exceptions(std::ios::failbit | std::ios::badbit);
-						mpt::IO::WriteRaw(file.stream(), mpt::as_span(std::string(updateScript)));
-					} catch(...)
-					{
-						throw Error(U_("Error creating update script."));
+						case 1:
+							usePowerShell = false;
+							break;
+						case 2:
+							usePowerShell = true;
+							break;
+						default:
+							// nothing
+							break;
 					}
-					std::vector<mpt::ustring> arguments;
-					arguments.push_back(U_("\"") + (dirTempOpenMPTUpdates + P_("update.vbs")).ToUnicode() + U_("\""));
-					arguments.push_back(U_("\"") + updateFilename.ToUnicode() + U_("\""));
-					arguments.push_back(U_("\"") + (downloadinfo.autoupdate_archive->subfolder.empty() ? U_(".") : downloadinfo.autoupdate_archive->subfolder) + U_("\""));
-					arguments.push_back(U_("\"") + theApp.GetInstallPath().WithoutTrailingSlash().ToUnicode() + U_("\""));
-					arguments.push_back(U_("\"") + downloadinfo.autoupdate_archive->restartbinary + U_("\""));
-					if(theApp.IsSourceTreeMode())
+					if(usePowerShell)
 					{
-						throw Warning(MPT_UFORMAT("Refusing to launch update '{} {}' when running from source tree.")(P_("cscript.exe"), mpt::join_format(arguments, U_(" "))));
-					}
-					if(reinterpret_cast<INT_PTR>(ShellExecute(NULL, NULL,
-						P_("cscript.exe").AsNative().c_str(),
-						mpt::ToWin(mpt::join_format(arguments, U_(" "))).c_str(),
-						dirTempOpenMPTUpdates.AsNative().c_str(),
-						SW_SHOWDEFAULT)) < 32)
+						try
+						{
+							mpt::IO::SafeOutputFile file(dirTempOpenMPTUpdates + P_("update.ps1"), std::ios::binary);
+							file.stream().imbue(std::locale::classic());
+							file.stream().exceptions(std::ios::failbit | std::ios::badbit);
+							mpt::IO::WriteRaw(file.stream(), mpt::as_span(std::string(updateScript_ps1)));
+						} catch(...)
+						{
+							throw Error(U_("Error creating update script."));
+						}
+						std::vector<mpt::ustring> arguments = {
+							U_("-NoLogo"),
+							U_("-ExecutionPolicy"), U_("Unrestricted"),
+							U_("\"") + (dirTempOpenMPTUpdates + P_("update.ps1")).ToUnicode() + U_("\""),
+							U_("-zip"), U_("\"") + updateFilename.ToUnicode() + U_("\""),
+							U_("-subfolder"), U_("\"") + (downloadinfo.autoupdate_archive->subfolder.empty() ? U_(".") : downloadinfo.autoupdate_archive->subfolder) + U_("\""),
+							U_("-dst"), U_("\"") + theApp.GetInstallPath().WithoutTrailingSlash().ToUnicode() + U_("\""),
+							U_("-restartbinary"), U_("\"") + downloadinfo.autoupdate_archive->restartbinary + U_("\"")
+						};
+						if(theApp.IsSourceTreeMode())
+						{
+							throw Warning(MPT_UFORMAT("Refusing to launch update '{} {}' when running from source tree.")(P_("cscript.exe"), mpt::join_format(arguments, U_(" "))));
+						}
+						if(reinterpret_cast<INT_PTR>(ShellExecute(NULL, NULL,
+							P_("PowerShell.exe").AsNative().c_str(),
+							mpt::ToWin(mpt::join_format(arguments, U_(" "))).c_str(),
+							dirTempOpenMPTUpdates.AsNative().c_str(),
+							SW_SHOWDEFAULT)) < 32)
+						{
+							throw Error(U_("Error launching update."));
+						}
+					} else
 					{
-						throw Error(U_("Error launching update."));
+						try
+						{
+							mpt::IO::SafeOutputFile file(dirTempOpenMPTUpdates + P_("update.vbs"), std::ios::binary);
+							file.stream().imbue(std::locale::classic());
+							file.stream().exceptions(std::ios::failbit | std::ios::badbit);
+							mpt::IO::WriteRaw(file.stream(), mpt::as_span(std::string(updateScript_vbs)));
+						} catch(...)
+						{
+							throw Error(U_("Error creating update script."));
+						}
+						std::vector<mpt::ustring> arguments;
+						arguments.push_back(U_("\"") + (dirTempOpenMPTUpdates + P_("update.vbs")).ToUnicode() + U_("\""));
+						arguments.push_back(U_("\"") + updateFilename.ToUnicode() + U_("\""));
+						arguments.push_back(U_("\"") + (downloadinfo.autoupdate_archive->subfolder.empty() ? U_(".") : downloadinfo.autoupdate_archive->subfolder) + U_("\""));
+						arguments.push_back(U_("\"") + theApp.GetInstallPath().WithoutTrailingSlash().ToUnicode() + U_("\""));
+						arguments.push_back(U_("\"") + downloadinfo.autoupdate_archive->restartbinary + U_("\""));
+						if(theApp.IsSourceTreeMode())
+						{
+							throw Warning(MPT_UFORMAT("Refusing to launch update '{} {}' when running from source tree.")(P_("cscript.exe"), mpt::join_format(arguments, U_(" "))));
+						}
+						if(reinterpret_cast<INT_PTR>(ShellExecute(NULL, NULL,
+							P_("cscript.exe").AsNative().c_str(),
+							mpt::ToWin(mpt::join_format(arguments, U_(" "))).c_str(),
+							dirTempOpenMPTUpdates.AsNative().c_str(),
+							SW_SHOWDEFAULT)) < 32)
+						{
+							throw Error(U_("Error launching update."));
+						}
 					}
 					wantClose = true;
 				} else
manx

manx

2025-06-14 06:38

administrator   ~0006396

r23483 / 1.33.00.06

Issue History

Date Modified Username Field Change
2023-09-11 17:22 Saga Musix New Issue
2023-09-11 22:25 Saga Musix Description Updated
2024-10-10 08:15 manx Category General => Installer and Update
2025-06-13 15:56 manx Assigned To => manx
2025-06-13 15:56 manx Status new => assigned
2025-06-13 15:58 manx Note Added: 0006393
2025-06-13 15:58 manx File Added: update-portable-powershell-v1.patch
2025-06-13 15:59 manx Target Version => OpenMPT 1.33 / libopenmpt 0.9 (goals)
2025-06-13 17:24 manx Note Added: 0006394
2025-06-13 17:25 manx Note Edited: 0006393
2025-06-13 17:48 manx Note Added: 0006395
2025-06-13 17:48 manx File Added: update-portable-powershell-v2.patch
2025-06-14 06:36 manx Note Edited: 0006393
2025-06-14 06:38 manx Status assigned => resolved
2025-06-14 06:38 manx Resolution open => fixed
2025-06-14 06:38 manx Fixed in Version => OpenMPT 1.33 / libopenmpt 0.9 (goals)
2025-06-14 06:38 manx Note Added: 0006396