Open vruss opened 3 months ago
What happens if you manually merge the content of generated manifest into app.manifest
?
I copied and replaced the contents of the generated one into the manifest but it didn't work.
I actually found a workaround just now. In the .csproj file I just need to change Isolated
to false
on the COM reference. This makes it so that no manifest file is generated and the manifest file I created works.
This however might break the COM reference if it is required to be Isolated
Update: setting the COM reference to not be Isolated
breaks my COM component. So now I have to choose between having a broken COM component and having a working manifest file.
This is really an SDK problem.
@AaronRobinsonMSFT hey thank you for transferring this issue to the correct team!
This has become a critical issue for the company I work for. Is there anything we can do to speed up the process? Contribute ourselves, sponsor or bounty on it?
Looking at the executables we can see that if we have the COM references isolated as true
then no Win32 resources are added to the executable. While the executable that had the COM refernces as isolated false
has the Win32 resource.
Maybe this lack of Win32 resources is what is causing the isolated true
to not load the manifest file?
Found another workaround / fix that works when running with <Isolated>True</Isolated>
!
$ rcedit "path-to-exe" --application-manifest "./path/to/app.manifest"
Running this program with that command add the missing Win32 resources to the <Isolated>True</Isolated>
built executable resulting in this structure:
And this is what the Win32 manifest looks like:
<?xml version="1.0" encoding="utf-8"?>
<assembly xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd" manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:co.v1="urn:schemas-microsoft-com:clickonce.v1" xmlns:co.v2="urn:schemas-microsoft-com:clickonce.v2">
<assemblyIdentity name="Net8.WinForms.exe" version="1.0.0.0" type="win32" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<applicationRequestMinimum>
<PermissionSet Unrestricted="true" ID="Custom" SameSite="site" />
<defaultAssemblyRequest permissionSetReference="Custom" />
</applicationRequestMinimum>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<file name="RxClientView.ocx" asmv2:size="10979304">
<hash xmlns="urn:schemas-microsoft-com:asm.v2">
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<dsig:DigestValue>DwijLRVRyiWmJP4/F+2mTIJWDJ4=</dsig:DigestValue>
</hash>
<typelib tlbid="{e12dc790-3a8e-456b-afde-ca8f570e740b}" version="1.0" helpdir="C:\src\dev3\Dependencies\Rastrex\RxClientView.hlp" resourceid="0" flags="CONTROL,HASDISKIMAGE" />
<comClass clsid="{86702dd4-6e0b-4d72-8715-c963f1ba38b3}" threadingModel="Apartment" tlbid="{e12dc790-3a8e-456b-afde-ca8f570e740b}" progid="RXCLIENTVIEW.RxClientViewCtrl.1" description="RxClientView Control" />
</file>
<file name="RxRedlines.dll" asmv2:size="1345592">
<hash xmlns="urn:schemas-microsoft-com:asm.v2">
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<dsig:DigestValue>u/tvvN735Z/2J+OO3wvqEon2SOg=</dsig:DigestValue>
</hash>
<typelib tlbid="{db83c810-c405-4df2-8949-b40b5bb65b71}" version="1.0" helpdir="" resourceid="0" flags="HASDISKIMAGE" />
<comClass clsid="{8710a9e5-c634-41b0-acd5-2f264b8fb50e}" threadingModel="Apartment" tlbid="{db83c810-c405-4df2-8949-b40b5bb65b71}" progid="RxRedlines.RxRed.1" description="RxRed Class" />
</file>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 GUID -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
<!--Padding to make filesize even multiple of 4 XXXX -->
Contribute ourselves, sponsor or bounty on it?
@vruss .NET is entirely open source. We welcome external contributions. Please reference the readme for details on this topic.
@marcpopMSFT Any chance we can get someone to look into this or provide some guidance on where @vruss can start?
Where I'd start is getting a binlog of the build for each scenario - .NET framework 4.8 and modern .NET (you'll need to rename the file after each build to prevent clobbering) - and find the Targets and Tasks responsible for creating that manifest. Whichever component owns those tasks is the next group to ping - I don't know who that is off the top of my head, and without a more explicit repro since I'm unfamiliar with the space I can't help navigate who it might be.
@baronfel thanks for the guidance! I took a binlog but I don't really know what I'm looking for so I think that's as far as I can go with this one.
I made a minimal repro and uploaded it here: https://github.com/vruss/net8-app-manifest-bug You can easily reproduce the error by building and starting the executables. Your should see that the UAC is missing in the NET8 build.
Thanks for the repo @vruss - I cloned it and did in fact notice the difference. The file is generated by the GenerateApplicationManifest
Task, which is part of the MSbuild repo itself, but hasn't had any meaningful changes in quite some time.
The generated manifests themselves look very similar, so nothing jumps out to me as obviously wrong. @rainersigwald since we own this Task, do you have any idea who we'd poke about Windows manifests and how they apply to .NET executables?
GenerateApplicationManifest
is part of ClickOnce so let's try @sujitnayak.
Ah wait, that's not the relevant manifest here though. This is the actual win32 manifest that needs to be embedded in the .exe
file. Let's poke at that a bit.
I don't have the repro right now--does it start working if you rename NET8.Console.dll.manifest
to NET8.Console.exe.manifest
?
@rainersigwald in my clone of the project renaming the manifest did not make UAC start triggering.
Ok we're somewhat out of my wheelhouse here but I think this is an SDK bug related to the differences in exe construction between .NET Framework and .NET apps.
For .NET Framework targets, the C# compiler itself emits a .exe
file, which has embedded in it the manifest (as a "Resource"). That file is copied to the output directory and can be run, in which case (I think) Windows will scan for an embedded manifest before starting to run user code (and thus see and require elevation).
For .NET targets, the C# compiler emits a .dll
file, which has the manifest embedded in it as a resource, and then creates a .exe
that is a copy of the prototypal apphost.exe
renamed to the application name. That is a small native application that loads the .NET 8 runtime and points it to the .dll
that contains your application code.
What I think is happening here is that the win32 manifest should ideally be embedded into the apphost .exe
so Windows can know to look for it before launching the application. That doesn't appear to be plumbed through in the SDK at the moment, and I think is the feature we'd need to make this scenario smooth.
A few things confuse me though:
mt.exe -inputresource:D:\net8-app-manifest-bug\NET48.Console\bin\Debug\net4.8\NET48.Console.exe -out:extracted.manifest
to see the embedded manifest of the net48 app. I thought that'd extract it the same way Windows does. Possibly a .NET Framework vs native win32 app behavior?NoWin32Manifest = True
). But that doesn't seem to work in either case (deleting the one next to the net48 app doesn't change anything and renaming the net8.0 one to match the .exe
doesn't help).If I do
> mt.exe -manifest NET8.Console.exe.manifest -outputresource:".\NET8.Console.exe;#1"
Microsoft (R) Manifest Tool
Copyright (c) Microsoft Corporation.
All rights reserved.
Then double-clicking on that app requests elevation. That feels like it confirms my hypothesis (but doesn't clear up any of my confusions).
At this point this feels like a gap that the HostWriter
class in the runtime should be handling for us, since we almost immediately delegate to that API in the CreateAppHost Task. @vitek-karas could we get your thoughts on this? Are we thinking in the right directions here?
If COM Isolation is set to true, the manifest will not be embedded in the EXE. It needs be an external file for the regfree COM registration to work. Setting COM Isolation to false works b/c the manifest gets embedded in the .NET 8.0 EXE. For the case where Isolated is set to true, it looks like the external manifest is generated fine but it is not getting probed for some reason when the EXE launches.
When looking at the two executables inside DotPeek you can see that the .NET8 executable is:
.NET Framework
.NET 8
@elinor-fung
I think @sujitnayak's comment is the key here: https://github.com/dotnet/sdk/issues/42027#issuecomment-2270356380
If you look at the NET8.Console.dll
that is produced when the COMReference
has Isolated=true
, it does not have have an embedded manifest - which means there will be no manifest in the resulting .exe, since HostWriter
just copies the resources from the .dll to the .exe.
I don't have the repro right now--does it start working if you rename
NET8.Console.dll.manifest
toNET8.Console.exe.manifest
?@rainersigwald in my clone of the project renaming the manifest did not make UAC start triggering.
I tried this and it did start triggering for me. But only if I hadn't already run it before renaming the manifest (not sure why - maybe Windows does some sort of caching).
Memories from Windows XP: procmon showed csrss.exe reading the manifest files so the cache was presumably in that process; and if the file write timestamp of the exe file had been changed, then it read the manifest file again instead of trusting cached data. Timestamps of the manifest file did not have the same effect.
Description
Update: Created a minimal repro here: https://github.com/vruss/net8-app-manifest-bug You will see that the Framework build works fine but the NET8 build doesn't run fine.
I have run into an issue with a Framework 4.8 to NET 8 upgrade that has to do with the Application Manifest not loading or working when I have COMReferences in my .csproj file.
Without the COMReferences:
With the COMReferences: Notice that I have 2 Application Manifest files after adding the COMReferences.
I have done a text-diff on both Manifest files when building a Framework 4.8 and NET 8 build and they are identical. This leads me to believe there is something wrong with the runtime of NET 8 and not the SDK.
App.manifest:
Generated manifest:
Reproduction Steps
requireAdministrator
to true. 2.1. Build and run and you will see the UAC prompt as expected.The UAC is just an easy way for me to verify if the Application Manifest is loaded. I noticed this issue because my Win10 compatibility was not working.
Example of what my csprojs looks like:
Expected behavior
I expect the Application Manifest to be respected and working in NET 8, the same way it did in Framework 4.8.
Actual behavior
The Application Manifest is ignored.
Regression?
Worked in Framework 4.8
Known Workarounds
Isolated false
Changing the COMReference import to be
<Isolated>False</Isolated>
fixed the issue for me.Isolated true
$ rcedit "path-to-exe" --application-manifest "./path/to/app.manifest"
Configuration
NET 8.0.204 Windows 10 x64 WinForms
Other information
No response