memstechtips/Winhance · error · InvalidOperationException
No update has been downloaded.
Error message
No update has been downloaded.
What it means
InvalidOperationException thrown by VersionService.LaunchInstallerAndRestart when the private _downloadedInstallerPath field is null/empty — i.e. the caller invoked the launcher without first successfully downloading the update installer. The method cannot launch what was not downloaded, so it refuses rather than launching nothing or a stale path.
Source
Thrown at src/Winhance.Infrastructure/Features/Common/Services/VersionService.cs:204
downloadRequest.Headers.TryAddWithoutValidation("User-Agent", _userAgent);
using var response = await _httpClient.SendAsync(downloadRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
// Use explicit block so streams are disposed before returning
{
await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None, 81920, true);
await contentStream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
_downloadedInstallerPath = tempPath;
_logService.Log(LogLevel.Info, $"Update downloaded to {tempPath}");
}
public void LaunchInstallerAndRestart()
{
if (string.IsNullOrEmpty(_downloadedInstallerPath))
throw new InvalidOperationException("No update has been downloaded.");
string appDir = AppContext.BaseDirectory;
bool isPortable = _fileSystemService.FileExists(_fileSystemService.CombinePath(appDir, "portable.marker"));
string appExePath = _fileSystemService.CombinePath(appDir, "Winhance.exe");
string installerArgs = BuildInstallerArgs(appDir, isPortable);
_logService.Log(LogLevel.Info, $"Launching installer (portable: {isPortable}), app will restart after install...");
// Use cmd /c to: run installer (wait for it to finish) then relaunch the app.
// The caller should exit the application immediately after this call.
var cmdArgs = $"/c start /wait \"\" \"{_downloadedInstallerPath}\" {installerArgs} && start \"\" \"{appExePath}\"";
Process.Start(new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = cmdArgs,
UseShellExecute = false,
CreateNoWindow = true
View on GitHub (pinned to f23d554eb2)
Solutions
- Gate the launch button on a successful download: only enable it after DownloadUpdateAsync sets the installer path.
- Call CheckForUpdateAsync first; if no update exists, do not offer the launch action.
- Persist the downloaded installer path (and verify the file still exists) across app restarts so a relaunch can resume.
- Surface the exception to the UI as 'No update downloaded yet — check for updates first.'
Example fix
// before
public void LaunchInstallerAndRestart()
{
if (string.IsNullOrEmpty(_downloadedInstallerPath))
throw new InvalidOperationException("No update has been downloaded.");
// after: explicit precondition with a recovery hint for the caller
public void LaunchInstallerAndRestart()
{
if (string.IsNullOrEmpty(_downloadedInstallerPath) || !_fileSystemService.FileExists(_downloadedInstallerPath))
throw new InvalidOperationException("No update has been downloaded. Call CheckForUpdateAsync/DownloadUpdateAsync first."); Defensive patterns
Strategy: validation
Validate before calling
// Gate the launch on a completed, file-present download.
bool IsUpdateReadyToLaunch() =>
!string.IsNullOrEmpty(_downloadedInstallerPath) && _fileSystemService.FileExists(_downloadedInstallerPath); Try / catch
catch (InvalidOperationException ex) when (ex.Message.Contains("No update has been downloaded"))
{
// Tell the user to check for updates first; the launch action should be disabled until then.
} Prevention
- Only enable the 'restart & update' button after DownloadUpdateAsync succeeds.
- Verify the downloaded installer file still exists before launching (it may have been cleaned).
- Persist the installer path across app restarts so a relaunch can resume.
When it happens
Trigger: LaunchInstallerAndRestart() is called before DownloadUpdateAsync (or CheckForUpdateAsync + download) completed successfully. The download was skipped because CheckForUpdateAsync reported no update, the download was cancelled, or a previous download's temp file was cleaned up between calls.
Common situations: A UI flow wires the 'restart & update' button directly to LaunchInstallerAndRestart without gating it on a completed download. A download failed silently (network) and _downloadedInstallerPath stayed null. The app was restarted (instance field reset) between download and launch.
Related errors
- ADK installation failed with exit code: {exitCode}
- winget install failed with exit code: {exitCode}
- winget install Microsoft.OSCDIMG failed with exit code: {exi
- Destination path cannot be empty.
- Destination path must include a directory.
AI-assisted analysis of memstechtips/Winhance@f23d554eb2 (2026-08-13).
Data as JSON: /api/errors/98a85a0935ebecbc.
Report an issue: GitHub.