Skip to content

Portable package upgrade fails under RedirectionGuard when canonicalizing WinGet Links symlink #6211

Description

@clairernovotny

Relevant area(s)

WinGet CLI, portable packages, upgrade

Relevant command(s)

winget update --id GitHub.Copilot --exact --source winget --accept-source-agreements --disable-interactivity --silent --include-unknown --accept-package-agreements --force

winget update --id Gyan.FFmpeg --exact --source winget --accept-source-agreements --disable-interactivity --silent --include-unknown --accept-package-agreements --force

Brief description of your issue

Upgrading user-scope portable zip packages fails after the archive is downloaded, hash-verified, and extracted. The failure happens while WinGet is handling the existing portable command alias in the user portable links directory.

Two different portable packages fail the same way:

An unexpected error occurred while executing the command:
weakly_canonical: The path cannot be traversed because it contains an untrusted mount point.: "%LOCALAPPDATA%\Microsoft\WinGet\Links\copilot.exe"
Installer failed with exit code: 0x8a150003 : Executing command failed
Process return value: "-1978335150" (0x8A150052)
An unexpected error occurred while executing the command:
weakly_canonical: The path cannot be traversed because it contains an untrusted mount point.: "%LOCALAPPDATA%\Microsoft\WinGet\Links\ffmpeg.exe"
Installer failed with exit code: 0x8a150003 : Executing command failed
Process return value: "-1978335150" (0x8A150052)

The existing aliases are normal user-scope symlinks created under %LOCALAPPDATA%\Microsoft\WinGet\Links, and their targets exist under %LOCALAPPDATA%\Microsoft\WinGet\Packages.

Examples:

%LOCALAPPDATA%\Microsoft\WinGet\Links\copilot.exe
  -> %LOCALAPPDATA%\Microsoft\WinGet\Packages\GitHub.Copilot_Microsoft.Winget.Source_8wekyb3d8bbwe\copilot.exe

%LOCALAPPDATA%\Microsoft\WinGet\Links\ffmpeg.exe
  -> %LOCALAPPDATA%\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.1-full_build\bin\ffmpeg.exe

A small local repro suggests this is an interaction between std::filesystem::weakly_canonical and Windows RedirectionGuard / ProcessRedirectionTrustPolicy. In a normal MSVC process, std::filesystem::weakly_canonical resolves the WinGet-created alias successfully. If the same test process first enables ProcessRedirectionTrustPolicy, it throws the same exception string WinGet logs.

Normal process:

%LOCALAPPDATA%\Microsoft\WinGet\Packages\GitHub.Copilot_Microsoft.Winget.Source_8wekyb3d8bbwe\copilot.exe
%LOCALAPPDATA%\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.1-full_build\bin\ffmpeg.exe

Process with ProcessRedirectionTrustPolicy enabled:

weakly_canonical: The path cannot be traversed because it contains an untrusted mount point.: "%LOCALAPPDATA%\Microsoft\WinGet\Links\copilot.exe"
weakly_canonical: The path cannot be traversed because it contains an untrusted mount point.: "%LOCALAPPDATA%\Microsoft\WinGet\Links\ffmpeg.exe"

I do not know whether WinGet is explicitly enabling that mitigation, inheriting it from the packaged process context, or being affected by a newer Windows build behavior. The observable result is that WinGet preview cannot upgrade existing user-scope portable packages whose aliases are symlinks created in the normal portable links directory.

Steps to reproduce

  1. Use WinGet preview v1.29.140-preview / DesktopAppInstaller v1.29.139.0.
  2. Have an older user-scope portable zip package installed with command aliases under %LOCALAPPDATA%\Microsoft\WinGet\Links. Reproduced with:
    • GitHub.Copilot installed at v1.0.24, upgrade available to v1.0.40
    • Gyan.FFmpeg installed at 8.1, upgrade available to 8.1.1
  3. Run one of:
winget update --id GitHub.Copilot --exact --source winget --accept-source-agreements --disable-interactivity --silent --include-unknown --accept-package-agreements --force
winget update --id Gyan.FFmpeg --exact --source winget --accept-source-agreements --disable-interactivity --silent --include-unknown --accept-package-agreements --force

Expected behavior

WinGet should upgrade the portable package and update/recreate its command aliases, or report a package-specific actionable failure.

Actual behavior

WinGet downloads, verifies, and extracts the archive, then fails during portable install with:

weakly_canonical: The path cannot be traversed because it contains an untrusted mount point.
Installer failed with exit code: 0x8a150003 : Executing command failed
Process return value: "-1978335150" (0x8A150052)

Relevant log lines:

[CLI ] Successfully extracted archive
[REPO] Opening database for ReadWrite at '%LOCALAPPDATA%\Microsoft\WinGet\Packages\GitHub.Copilot_Microsoft.Winget.Source_8wekyb3d8bbwe\GitHub.Copilot_Microsoft.Winget.Source_8wekyb3d8bbwe.db'
[REPO] Opened Portable Index with version [1.0]
[CLI ] Caught std::exception: weakly_canonical: The path cannot be traversed because it contains an untrusted mount point.: "%LOCALAPPDATA%\Microsoft\WinGet\Links\copilot.exe"
[CLI ] Portable installer failed: 2316632067
[CLI ] Terminating context: 0x8a150052 at C:\__w\1\s\external\pkg\src\AppInstallerCLICore\Workflows\InstallFlow.cpp:237

The Gyan.FFmpeg failure is the same, except the alias path is %LOCALAPPDATA%\Microsoft\WinGet\Links\ffmpeg.exe.

Minimal repro for the underlying filesystem exception

This does not use WinGet internals. It demonstrates that Windows rejects weakly_canonical on the existing WinGet-created symlink when ProcessRedirectionTrustPolicy is enabled.

#include <filesystem>
#include <iostream>
#include <windows.h>

int wmain(int argc, wchar_t** argv)
{
    if (argc != 2)
    {
        std::wcerr << L"usage: redirection_trust_probe <path>\n";
        return 2;
    }

    PROCESS_MITIGATION_REDIRECTION_TRUST_POLICY policy{};
    policy.EnforceRedirectionTrust = 1;
    if (!SetProcessMitigationPolicy(ProcessRedirectionTrustPolicy, &policy, sizeof(policy)))
    {
        std::cerr << "SetProcessMitigationPolicy failed: " << GetLastError() << "\n";
        return 3;
    }

    try
    {
        std::wcout << std::filesystem::weakly_canonical(argv[1]).wstring() << L"\n";
        return 0;
    }
    catch (const std::exception& e)
    {
        std::cerr << e.what() << "\n";
        return 1;
    }
}

Compile:

cl /std:c++17 /EHsc redirection_trust_probe.cpp /Fe:redirection_trust_probe.exe

Run:

redirection_trust_probe.exe "%LOCALAPPDATA%\Microsoft\WinGet\Links\copilot.exe"
redirection_trust_probe.exe "%LOCALAPPDATA%\Microsoft\WinGet\Links\ffmpeg.exe"

Environment

Windows Package Manager (Preview) v1.29.140-preview
Windows: Windows.Desktop v10.0.26300.8346
System Architecture: X64
Package: Microsoft.DesktopAppInstaller v1.29.139.0

Portable Links Directory (User)    %LOCALAPPDATA%\Microsoft\WinGet\Links
Portable Package Root (User)       %LOCALAPPDATA%\Microsoft\WinGet\Packages

Metadata

Metadata

Assignees

No one assigned

    Labels

    Command-UpgradeIssue related to WinGet UpgradePortableIssue related to portable package

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions