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

  1. Gate the launch button on a successful download: only enable it after DownloadUpdateAsync sets the installer path.
  2. Call CheckForUpdateAsync first; if no update exists, do not offer the launch action.
  3. Persist the downloaded installer path (and verify the file still exists) across app restarts so a relaunch can resume.
  4. 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

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


AI-assisted analysis of memstechtips/Winhance@f23d554eb2 (2026-08-13). Data as JSON: /api/errors/98a85a0935ebecbc. Report an issue: GitHub.