Understanding the PowerToys Update Mechanism and Auto-Update Flow

PowerToys coordinates updates between the Settings UI and the Runner process, supporting both manual checks triggered by GeneralViewModel.CheckForUpdatesClick and automatic background checks via PeriodicUpdateWorker in UpdateUtils.cpp, with downloads and notifications controlled by Group Policy and metered connection settings.

The auto-update system in the microsoft/PowerToys repository ensures users receive the latest features and security patches without manual intervention. This mechanism relies on a split architecture where the Settings UI handles user interactions and the Runner manages background update logic. Understanding how these components communicate via IPC and process GitHub release data reveals how PowerToys maintains itself across millions of devices.

How the PowerToys Update System Works

The update architecture separates concerns between the user-facing Settings UI (C#) and the background Runner process (C++). The Settings UI captures user input and forwards requests via IPC, while the Runner owns the actual update logic, network requests, and state persistence.

Key components include:

Manual Update Checks

When a user clicks Check for updates in the Settings UI, the request flows through three distinct layers before reaching GitHub's API.

Triggering the Check from the UI

In GeneralViewModel.cs, the CheckForUpdatesClick method serializes the request and forwards it via IPC:

// GeneralViewModel.cs – manual trigger
private void CheckForUpdatesClick()
{
    SendCheckForUpdatesConfigMSG(customaction.ToString());
}

IPC Transmission to the Runner

The ShellPage.xaml.cs file registers the IPC callback via SetCheckForUpdatesMessageCallback, which forwards the UI request to the Runner process. This decouples the UI from the network logic, ensuring the Runner handles all HTTP requests with appropriate system privileges.

Runner Processing

Upon receiving the IPC message, the Runner invokes CheckForUpdatesCallback in src/runner/UpdateUtils.cpp. This function retrieves the current state, queries GitHub for the latest release, and processes the result:

// UpdateUtils.cpp – callback entry point
void CheckForUpdatesCallback()
{
    Logger::trace(L"Check for updates callback invoked");
    auto state = UpdateState::read();
    auto new_version_info = std::move(get_github_version_info_async()).get();

    if (!new_version_info)
    {
        state.state = UpdateState::networkError;
        Logger::error(L"Couldn't obtain version info from github: {}", new_version_info.error());
    }
    else
    {
        bool download_update = !IsMeteredConnection() && get_general_settings().downloadUpdatesAutomatically;
        // GPO may override automatic download
        if (powertoys_gpo::getDisableAutomaticUpdateDownloadValue() == powertoys_gpo::gpo_rule_configured_enabled)
            download_update = false;

        bool updateAvailable = std::holds_alternative<new_version_download_info>(*new_version_info);
        ProcessNewVersionInfo(*new_version_info, state, download_update, false);
    }

    UpdateState::store([&](UpdateState& v) { v = std::move(state); });
}

The callback respects Group Policy Objects (GPO) via powertoys_gpo::getDisableAutomaticUpdateDownloadValue and checks for metered connections using IsMeteredConnection() before attempting downloads.

Automatic Background Updates

Beyond manual checks, PowerToys runs a detached thread at startup that executes PeriodicUpdateWorker in UpdateUtils.cpp. This worker operates independently of user interaction.

The worker sleeps for UPDATE_CHECK_INTERVAL_MINUTES (24 hours by default) between checks. If a check fails, it uses a shorter back-off interval defined by UPDATE_CHECK_AFTER_FAILED_INTERVAL_MINUTES.

void PeriodicUpdateWorker()
{
    for (;;)
    {
        auto state = UpdateState::read();
        // …calculate sleep interval…
        std::this_thread::sleep_for(std::chrono::minutes{ sleep_minutes_till_next_update });

        // Auto‑download decision
        bool download_update = !IsMeteredConnection() && get_general_settings().downloadUpdatesAutomatically;
        if (powertoys_gpo::getDisableAutomaticUpdateDownloadValue() == powertoys_gpo::gpo_rule_configured_enabled)
            download_update = false;

        // Retrieve version info from GitHub
        const auto new_version_info = std::move(get_github_version_info_async()).get();
        if (new_version_info.has_value())
        {
            ProcessNewVersionInfo(*new_version_info, state, download_update, true);
            UpdateState::store([&](UpdateState& v){ v = std::move(state); });
        }
        else
        {
            // retry after a short back‑off
            std::this_thread::sleep_for(std::chrono::minutes{ UPDATE_CHECK_AFTER_FAILED_INTERVAL_MINUTES });
        }
    }
}

Like the manual flow, the periodic worker respects GPO settings and metered connection status before calling download_new_version_async.

Core Update Processing Logic

The ProcessNewVersionInfo function serves as the central decision engine for both manual and automatic flows. It manages the UpdateState machine and handles:

  • Recording check timestamps via state.githubUpdateLastCheckedDate.emplace(timeutil::now())
  • Detecting up-to-date status when github_version_info contains version_up_to_date
  • Handling new versions by extracting new_version_download_info
  • GPO enforcement for toast notifications via powertoys_gpo::getDisableNewUpdateToastValue and powertoys_gpo::getSuspendNewUpdateToastValue
  • Auto-download logic using download_new_version_async when download_update is true and the user setting downloadUpdatesAutomatically is enabled
  • Toast creation via ShowNewVersionAvailable (which uses the custom URI powertoys://update_now/) or ShowOpenSettingsForUpdate (using powertoys://open_overview/)

When auto-download succeeds, the installer is saved locally and a toast notification offers an Update now action. If auto-download is disabled, the toast directs users to the Settings page instead. The final installation is handled by LaunchPowerToysUpdate, which executes PowerToys.Update.exe.

UI Integration and State Management

The Settings UI consumes update state through data binding and converters. The UpdateStateToBoolConverter in src/settings-ui/Settings.UI/Converters/UpdateStateToBoolConverter.cs transforms the persisted UpdateState into boolean values that drive UI visibility for update banners and buttons.

State persistence occurs in src/common/updating/updateState.h and updateState.cpp, which provide UpdateState::read() and UpdateState::store() methods. These ensure update availability survives application restarts, allowing the UI to immediately show update status on launch without re-querying GitHub.

The IPC bridge in ShellPage.xaml.cs registers callbacks via SetCheckForUpdatesMessageCallback, creating the communication channel that allows the C# UI to invoke C++ update logic in the Runner process.

Summary

  • PowerToys uses a split architecture where the Settings UI handles user input and the Runner manages background update logic in UpdateUtils.cpp.
  • Manual checks originate in GeneralViewModel.cs, travel via IPC through ShellPage.xaml.cs, and execute in CheckForUpdatesCallback.
  • Automatic checks run every 24 hours via PeriodicUpdateWorker, with shorter retry intervals after failures.
  • GPO settings and metered connection status can disable automatic downloads via getDisableAutomaticUpdateDownloadValue and IsMeteredConnection().
  • The UpdateState machine persists across sessions in src/common/updating/updateState.cpp, while UpdateStateToBoolConverter drives the UI visibility.

Frequently Asked Questions

How often does PowerToys check for updates automatically?

The PeriodicUpdateWorker in src/runner/UpdateUtils.cpp sleeps for UPDATE_CHECK_INTERVAL_MINUTES (24 hours by default) between checks. If a check fails due to network errors, it uses a shorter back-off period defined by UPDATE_CHECK_AFTER_FAILED_INTERVAL_MINUTES before retrying.

Can I disable automatic updates in PowerToys?

Yes, automatic updates can be disabled through Group Policy or the Settings UI. The Runner checks powertoys_gpo::getDisableAutomaticUpdateDownloadValue() in UpdateUtils.cpp, which returns gpo_rule_configured_enabled when disabled by policy. Additionally, users can toggle automatic downloads in the General settings, though GPO settings override user preferences.

What happens if I'm on a metered connection?

PowerToys respects Windows metered connection settings via the IsMeteredConnection() helper. When on a metered connection, the download_update boolean evaluates to false in both CheckForUpdatesCallback and PeriodicUpdateWorker, preventing automatic downloads of the installer. Notifications about available updates may still appear depending on toast settings, but the actual binary will not download until the connection is no longer metered.

How does the Settings UI know an update is available?

The UI relies on the UpdateState persistence layer defined in src/common/updating/updateState.h and updateState.cpp. When the Runner processes a new version via ProcessNewVersionInfo, it stores the state using UpdateState::store(). The Settings UI reads this state on startup and uses the UpdateStateToBoolConverter to transform the raw state into boolean properties that drive visibility of update banners and the "Update now" button.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →