Private SxS Components (registration-free COM)

Brass Contributor

My application has private SxS components that it includes with an Application Manifest. This manifest includes dependentAssembly nodes that refer to COM DLLs in the same folder that have embedded assembly manifests. This all works fine outside of MSIX, but fails with a standard SxS error when packaged (The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail). 

 

Looking at the goings-on in Procmon, I can see that it's the 'csrss.exe' Windows component that is performing the lookup for the dependencies, and since this lives outside the container and is not invoked by my app directly (it's always running in the background), it is looking in the real ProgramFilesX86 folder rather than the one in the VFS that actually contains the DLLs. 

 

Does anyone have any suggestions on what the best way to handle this would be? I've tried the PSF FileRedirectionFixup and DynamicLibraryFixup in PSF, which don't work - I suspect this is because csrss isn't something that is being created from my application, but rather is something running in Windows and handling SxS, and so it isn't being intercepted. The bitness doesn't always match either, so I don't think PSF could intercept it even if it wanted to (my app is x86 due to 3rd part native components, but csrss is x64 on an x64 system).

 

My only fallback is to munge the VREG such that the components are registered 'globally' rather than privately (which is fine for MSIX as it's scoped to the container), but that means I need to have different Application Manifest files depending on if I'm packaging MSI versus MSIX (I ship both) since the exe will be expecting SxS as-is. 

8 Replies
I can confirm that applications with private manifests under MSIX seem to fall victim to the error message you mention (but not always). There is nothing in the PSF to help with this issue today, even after devoting a lot of time fruitlessly debugging these situations.

My normal procmon trace excludes the csrss process, so I hadn't noticed it's involvement. Perhaps that will prove important.
If it helps, this is only an issue for my .NET COM components as the SxS stuff is pretty strict about the presence of assemblyIdentity in the manifest; the native components are perfectly happy being registered directly in the application manifest rather than via dependentAssembly with embedded assembly manifests, whereas the .NET stuff requires a manifest per component since it requires assemblyIdentity, which can only appear once in a manifest.

I guess I will have to just update the CI pipeline to use different manifests for MSI versus MSIX, and register the offending components in the VREG for the latter (I've quickly proven this out by hand and it works fine for my case). Not ideal, but it works.

@JDHIntercede Sounds like the right approach.

 

I'm running into this in trying to repackage existing MSIs into MSIX.  Can you describe how to tell if the manifest is for registration-free COM?  A sample manifest would go a long way to help me understand this.

@TIMOTHY MANGAN 

It pretty-much comes down to the elements inside it.

You can extract a manifest using mt.exe (https://docs.microsoft.com/en-us/windows/win32/sbscs/mt-exe) with the following:

 

mt.exe -inputresource:TheApplication.exe;#1 -out:extracted.manifest

 

 

If you open up the file you extracted, you're looking for elements like <comClass>, <typelib>, <comInterfaceExternalProxyStub> (docs here; schema is here). 

 

I've found that registrations directly inside the application manifest (i.e. the manifest in the application executable) seem to work fine. The issue comes about when the manifest is referencing external manifests, even if these are private and in the same folder. To identify this kind of thing, look for something like:

 

	<dependency>
		<dependentAssembly>
			<assemblyIdentity name="SomeComponent" version="1.2.3.4" publicKeyToken="00000a0000cb0d0c" processorArchitecture="x86"/>
		</dependentAssembly>
	</dependency>

 

 

These entries are saying I depend on 'SomeComponent' with the identity specified, and the loader will look first in the GAC, then in the current directory for a DLL/manifest with the same name (this is the bit that's not respecting the VFS in MSIX), then, if not found, in a subdirectory with the same name. Side note: regardless of MSIX, if it finds a DLL with the same name, but without an embedded manifest, before it finds a standalone manifest file, it will fail - this can cause problems where the manifest file is standalone rather than embedded, as the loader stops looking after the failure even if the manifest exists.

@JDHIntercede Yeah, I've been digging into this a little.

 

I am fairly sure that you should be able to use the components with either internal or external application and component manifests in your MSIX package AS IS. 

 

The trick will be to add the com registration directly into the AppXManifest.  This, in essence, it the modern way to advertise the com components, whether or not they originally were registration-free or registered in the registry.  It allows for side-by-side deployment in the package, but the installation of the MSIX package writes the registration from the AppXManifest into a new per-package location in the registry to make things easy to clean up.

 

@TIMOTHY MANGAN Unfortunately, if the exe has an embedded application manifest that is expecting private SxS registration then it absolutely will not work as-is with the components registered 'normally' inside the container, either leveraging the VREG or the AppXManifest. Believe me, I've tried - I picked up this task I estimated at 4hrs last Tuesday, and I just got it all working nearly a week later. 

My main build produces an MSI, which I then use the workspace from to create my MSIX. I had to update my build to:

  • Remove the signature from the MSI exe
  • Extract the manifest
  • Remove the <dependency> nodes
  • Re-embed the manifest in the exe
  • Re-sign the exe

Then my components are registered as normal in the MSIX VREG, with the files placed in the VFS in their 'global' locations, which removes the need for the <dependency> nodes in the first place. MSI keeps the <dependency> nodes as I want that to have private SxS registration without exposing interfaces to the wider system. 

I think the root cause of all this is where Windows reports the process as being run from to external callers. If you run an MSIX-packaged app then use the .NET Process.GetProcessById method to get a handle to it, then check where it's located, you'll see it returns the real equivalent of any VFS location rather than the actual path inside the WindowsApps folder. I first encountered this with one of our own apps that checks the signature of the caller, and we have to basically check if the process is associated with a package (GetPackageFullName function (appmodel.h) - Win32 apps | Microsoft Docs) then check in the known subfolder of the package install location for the reported exe, and, if it's not in a package, just check the path Windows reported. You can literally get a situation where Windows reports a process is running from a location that doesn't exist.

@JDHIntercede Regarding csrss...

 

This is a session process.  It will only attempt to read an external manifest file from a folder once during the life of the user session.  I am seeing it successfully read the manifest file from the MSIX package during that first run.  Subsequent launches don't look for the file again.

 

This doesn't explain the problem or eliminate the problem.  I just want to make sure you didn't jump to incorrect conclusions.

That explains the behaviour I saw when I put the DLLs into the real file-system where csrss was looking for them (it worked and then continued to work after I deleted them - confused me for a little while that one did!). Possibly also indicates that while csrss is reading the referenced dependency manifests from the real file-system, the application itself is able to find the DLLs inside the VFS (otherwise I'd assume it would have stopped working after deleting the files from the real FS).