[UWP] IPC with pipes inbetween packaged UWP app and Desktop .Net 6.0 program

Tyler Fonzi 26 Reputation points
2022-03-02T22:27:19.88+00:00

A couple questions about the intricacies of doing pipe IPC in UWP (Don't tell me to use an app service, it's too low latency of a solution for my project).

I'm trying to get working IPC between two of my projects, one of them being a UWP packaged app, and the other being a C# .net6 app. I have successfully created a NamedPipeServerStream inside of my .net6 program. Initially, my UWP side pipe client (NamedPipeClientStream) would fail during creation due to a "Access to the path is denied" error.

Doing research, I have deduced that I need to add some access rules related to my UWP package's SID so that my UWP app can successfully communicate with my external app. It seems vanilla .net NamedPipeServerStream does not allow for access rules to be added via constructor, but I was able to work around it with a nuget package. I was able to get a SID using a function described in the docs, although this turned out to be the wrong SID. Using a hard coded SID from the powershell command "CheckNetIsolation.exe LoopbackExempt -s", I was able to successfully create the NamedPipeClientStream object in my UWP app, getting past the access denied error.

However, the connect method that I call directly after does not succeed. I end up getting an error:

"Could not load file or assembly 'System.Security.Principal.Windows, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)"

A couple questions:

  1. Is making a pipe between the two projects actually this difficult? Am I close to the light at the end of the tunnel?
  2. Why did my two methods of receiving the SID for my packaged app result in different SIDS? Is there a nice programmatic way in C# to do this?

Here are some of my code samples:

Server:

                                            //IntPtr ptr = IntPtr.Zero;  
						//var result = DeriveAppContainerSidFromAppContainerName("PackageFamilyName", ref ptr);  
                                            //var appSid = new SecurityIdentifier(ptr); //results in incorrect SID  

						var appSid = new SecurityIdentifier("hardCodedSID");  
						var access = new PipeSecurity();  
						SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);  
						access.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.FullControl, System.Security.AccessControl.AccessControlType.Allow));  
						access.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.AccessSystemSecurity, System.Security.AccessControl.AccessControlType.Allow));  
						access.AddAccessRule(new PipeAccessRule(appSid, PipeAccessRights.FullControl, System.Security.AccessControl.AccessControlType.Allow));  
						access.AddAccessRule(new PipeAccessRule(appSid, PipeAccessRights.AccessSystemSecurity, System.Security.AccessControl.AccessControlType.Allow));  
						pipeServer = NamedPipeServerStreamConstructors.New("testpipe", PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.None, 0, 0, access);  
						pipeServer.WaitForConnection(); // gets past here when client creates NamedPipeClientStream  

Client:

                                            pipeClient = new NamedPipeClientStream(".", "testpipe", PipeDirection.InOut, PipeOptions.Asynchronous);  
                                            pipeClient.Connect(5000); // fails here  
Developer technologies | Universal Windows Platform (UWP)

2 answers

Sort by: Most helpful
  1. Tyler Fonzi 26 Reputation points
    2022-03-04T14:12:01.47+00:00

    I was able to get somewhat of a working solution doing independent research. A couple answers:

    1. NamedPipeServerStreamConstructors.New via NamedPipeServerStream.NetFrameworkVersion is a deprecated strategy. .Net 6.0 provides what I was trying to do via NamedPipeServerStreamACL. I opted to use this instead of what is currently in my sample.
    2. I could not figure out the error with "System.Security.Principal.Windows" that was happening in my UWP app when trying to connect using NamedPipeClientStream. I think it just might be a bug or something. If anyone knows what is going on there, would be appreciated.
    3. To work around #2, I implemented a lower level version of what I was trying to do using CreateFileW. This is a lower level c++ function, whilst the previous is the C# equivalent. It appears that the C# version has some intelligence with the "\.\pipe\" prefix, so it is not needed when creating the pipe server. However, the C++ function in the client code for UWP will need to include that (no use of LOCAL prefix was needed). With this set up, I was able to send requests from my .Net 6.0 pipe server to my UWP pipe client and get responses back. Towards the end are some code snippets:
    4. Still do not know the answer as to why I got the incorrect SID via DeriveAppContainerSidFromAppContainerName. I looked in my Registry and could not even located the SID it was trying to point me at. Mega confused.

    Client code:

    var fileName = @"\\.\pipe\\testpipe";  
    IntPtr pipeHandle = CreateFileW(fileName,  
            GENERIC_READ | GENERIC_WRITE,  
     FILE_SHARE_READ | FILE_SHARE_WRITE,  
     IntPtr.Zero,  
     OPEN_EXISTING,  
     FILE_FLAG_OVERLAPPED,  
     IntPtr.Zero);  
    if (pipeHandle.ToInt32() == INVALID_HANDLE_VALUE)  
    {  
     var err = $"Failed to create pipe client, win32 error: {Marshal.GetLastWin32Error()}";  
     throw new Exception(err);  
    }  
    stream = new FileStream(new SafeFileHandle(pipeHandle, true), FileAccess.ReadWrite);  
    

    Server construction code:

    NamedPipeServerStream pipeServer = NamedPipeServerStreamAcl.Create("testpipe", PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous, 512, 512, access);  
    

    Was this answer helpful?

    1 person found this answer helpful.

  2. Baka632 101 Reputation points
    2026-06-30T11:01:16.8966667+00:00

    I've recently looked into this issue and have some new findings.

    Your scenario is that the desktop app creates the pipe, and the UWP app connects to it. The key is to configure the proper permissions for the UWP app when the desktop app creates the pipe.

    On the UWP side, your original code is fine:

    pipeClient = new NamedPipeClientStream(".", "testpipe", PipeDirection.InOut, PipeOptions.Asynchronous);
    pipeClient.Connect(5000);
    

    As long as you use the correct package family name, the SID returned by DeriveAppContainerSidFromAppContainerName should be correct. If you got an incorrect SID, it was likely because you passed the wrong package family name.

    For example, Baka632.UwpPipe.NetNative_xrr5xv0r1z0t2 is the package family name; Baka632.UwpPipe.NetNative_1.0.1.0_x64__xrr5xv0r1z0t2 is the package full name; and Baka632.UwpPipe.NetNative is the package name. Only the package family name (here Baka632.UwpPipe.NetNative_xrr5xv0r1z0t2) yields the correct SID – none of the others work.

    It might also be a P/Invoke marshaling issue. I used CsWin32 to generate the P/Invoke code, which makes it easier to obtain the SID:

    if (!string.IsNullOrWhiteSpace(packageFamilyName)
                && PInvoke.DeriveAppContainerSidFromAppContainerName(packageFamilyName, out PSID psid).Succeeded)
    {
        SecurityIdentifier appSid;
        unsafe
        {
            appSid = new((nint)psid.Value);
            PInvoke.FreeSid(psid); // The PSID must be freed; this does not affect the already created SecurityIdentifier.
        }
        return packageSid;
    }
    

    The desktop app's pipe creation code is as follows. Unlike your version, you don't need to add WorldSid – just the current user's SID is sufficient. You also don't need broad permissions; read/write access is enough.

    PipeSecurity access = new();
    SecurityIdentifier appSid; // The UWP app's SID obtained earlier.
    SecurityIdentifier currentUserSid = WindowsIdentity.GetCurrent().Owner ?? throw new InvalidOperationException("Unable to get the current user's SID.");
    
    PipeAccessRights rights = PipeAccessRights.Read | PipeAccessRights.Write;
    access.AddAccessRule(new PipeAccessRule(appSid, rights, AccessControlType.Allow));
    access.AddAccessRule(new PipeAccessRule(currentUserSid, rights, AccessControlType.Allow));
    NamedPipeServerStream pipeStream = NamedPipeServerStreamAcl.Create(
                        "testpipe", PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte,
                        PipeOptions.Asynchronous, 512, 512,
                        access, HandleInheritability.None);
    

    Additionally, I've written a detailed demo project for UWP pipe communication, which you can refer to: Baka632/UWP-Pipe.


    Now, regarding the System.Security.Principal.Windows package:

    The latest version (5.0.0) conflicts with UWP dependencies, causing the compiler to reference the wrong assembly version, which leads to application crashes. (In fact, the compiler also shows a MSB3277 warning after installing this package as a reminder.)

    If your UWP client only connects to a pipe created by a desktop app, you don't need any extra package – you can simply uninstall it.

    If you cannot uninstall it, downgrade to version 4.7.0.

    You also mentioned the NamedPipeServerStream.NetFrameworkVersion package. If your UWP app needs to reference it, use version 1.0.10, because its latest version also references System.Security.Principal.Windows 5.0.0.

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.