dotnet / dotnet/sdk

ProjectReference copies AppHostIntermediatePath instead of output, causing referenced EXE to not reflect post-build changes (workaround included)

Open
#54,654 0 comments 0 reactions 0 assignees View on GitHub
Area-Workloads build
Dominant language
C#
Stars
3.2k
Forks
1.3k
PR merge metrics
PR metrics pending

Description

### Describe the bug

When a project reference points to another project that produces an EXE (for example, B.exe in a classic A->B reference scenario), the referenced EXE that gets copied is sourced from the AppHostIntermediatePath (i.e., the intermediate folder), not from the final output location. If the referenced project performs post-build modifications to B.exe (such as code-signing or updating the timestamp in the output directory), those changes are not reflected in the copy consumed by A. Other outputs like B.dll are always copied from the output path, and thus always include any post-build mutations.

This results in inconsistencies and is surprising to consumers who expect both EXE and DLL artifacts of a referenced project to behave the same way.

### Steps to reproduce

1. Unzip the attached repro with two projects, 'A' and 'B'.
2. Build project A (which references B) as normal.
3. Observe that B's post-build step simulates 'signing' by touching both B.dll and B.exe in the output directory, ensuring their timestamps differ from unmodified output.
4. Notice that in A's output folder, B.dll is the signed/touched version from B's output, but B.exe is not: its timestamp still matches an intermediate copy, not the output 'signed' B.exe. This shows the ProjectReference copy for B.exe uses the AppHostIntermediatePath as the source.
5. Now, build with /p:ApplyWorkaround=true. Observe that both DLL and EXE outputs now have correct, matched (signed/touched) timestamps in A's output folder.

[pr-host-repro.zip](https://github.com/user-attachments/files/28766662/pr-host-repro.zip)

### Expected behavior

Referenced EXE (e.g., B.exe) from a ProjectReference should be copied from the final output path of the referenced project, the same as DLLs, so any changes or signing applied after build in the referenced project's output will be picked up correctly. Both EXE and DLL should have consistent post-build behavior.

### Actual behavior

Currently, the EXE (B.exe) taken via ProjectReference is not the version from the output directory, but the one generated (and possibly cached) in the intermediate directory. Any modifications made to B.exe in the final output—such as simulated signing/timestamps—are ignored for referencing projects. DLLs behave correctly, so this appears to be a copy-source inconsistency.

### Is this a regression?

Not sure if this is a regression from a prior SDK version; behavior observed in recent SDKs.

### Are there any workarounds?

A workaround is possible by disabling the optimization (`MSBuildDisableGetCopyToOutputDirectoryItemsOptimization`) that avoids flowing metadata across project references and injecting custom metadata so the referencing project can determine and copy from the output path instead. See attached workaround in the repro for details.

### dotnet --info output

```console
dotnet --info
.NET SDK:
Version: 11.0.100-preview.4.26208.110
Commit: 0cf6b19ed6
Workload version: 11.0.100-manifests.61341afd
MSBuild version: 18.6.0-preview-26208-110+0cf6b19ed

Runtime Environment:
OS Name: Windows
OS Version: 10.0.26200
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\11.0.100-preview.4.26208.110\

.NET workloads installed:
[maui-windows]
Installation Source: VS 18.8.11807.88
Manifest Version: 11.0.0-preview.1.26102.3/11.0.100-preview.1
Manifest Path: C:\Program Files\dotnet\sdk-manifests\11.0.100-preview.1\microsoft.net.sdk.maui\11.0.0-preview.1.26102.3\WorkloadManifest.json
Install Type: Msi

[maccatalyst]
Installation Source: VS 18.8.11807.88
Manifest Version: 26.2.11310-net11-p1/11.0.100-preview.1
Manifest Path: C:\Program Files\dotnet\sdk-manifests\11.0.100-preview.1\microsoft.net.sdk.maccatalyst\26.2.11310-net11-p1\WorkloadManifest.json
Install Type: Msi

[ios]
Installation Source: VS 18.8.11807.88
Manifest Version: 26.2.11310-net11-p1/11.0.100-preview.1
Manifest Path: C:\Program Files\dotnet\sdk-manifests\11.0.100-preview.1\microsoft.net.sdk.ios\26.2.11310-net11-p1\WorkloadManifest.json
Install Type: Msi

[android]
Installation Source: VS 18.8.11807.88
Manifest Version: 36.1.99-preview.1.119/11.0.100-preview.1
Manifest Path: C:\Program Files\dotnet\sdk-manifests\11.0.100-preview.1\microsoft.net.sdk.android\36.1.99-preview.1.119\WorkloadManifest.json
Install Type: Msi

Configured to use workload sets when installing new manifests.
No workload sets are installed. Run "dotnet workload restore" to install a workload set.

Host:
Version: 11.0.0-preview.4.26208.110
Architecture: x64
Commit: 0cf6b19ed6

.NET SDKs installed:
10.0.204 [C:\Program Files\dotnet\sdk]
10.0.300-preview.0.26217.103 [C:\Program Files\dotnet\sdk]
11.0.100-preview.4.26208.110 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 10.0.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 10.0.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 11.0.0-preview.4.26208.110 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 8.0.27 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 10.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 10.0.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 11.0.0-preview.4.26208.110 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 8.0.27 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 10.0.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 10.0.8 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 11.0.0-preview.4.26208.110 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
Not set

global.json file:
Not found

Learn more:
https://aka.ms/dotnet/info

Download .NET:
https://aka.ms/dotnet/download
```

### IDE version

_No response_

### Other details

Root cause appears to be inclusion of in https://github.com/dotnet/sdk/blob/main/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets, which makes ProjectReference copy from the intermediate folder for EXEs. Would expect output path to be used, like with DLLs.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.