Skip to content

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

@clairernovotny

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

    Needs-TriageIssue needs to be triaged

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions