Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NamedPipeServerStream not working on Windows 10 (works in Windows 7)

It seems that NamedPipeServerStream is not working on Windows 10.

I'm using the following code to create a named pipe from my C# application. This code has been copied directly from the MSDN example, so should be correct, I suppose:

class Program
{
    static void Main(string[] args)
    {
        using (NamedPipeServerStream pipeServer =
                new NamedPipeServerStream("testpipe", PipeDirection.Out))
        {
            Console.WriteLine("NamedPipeServerStream object created.");
            Console.Write("Waiting for client connection... ");
            pipeServer.WaitForConnection();
            Console.WriteLine("Client connected.");
            try
            {
                using (StreamWriter sw = new StreamWriter(pipeServer))
                {
                    sw.AutoFlush = true;
                    sw.WriteLine("Hallo world!");
                    Console.WriteLine("Data was written.");
                }
            }
            catch (IOException e)
            {
                Console.WriteLine("{0}: {1}", e.GetType().Name, e.Message);
            }
            Console.WriteLine("Pipe closed.");
        }
    }
}

Now, if I run this program, the pipe is created successfully. But, on Windows 10, every attempt to read from the pipe in a terminal fails immediately with error "All pipe instances are busy":

Microsoft Windows [Version 10.0.17134.228]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Users\MuldeR>type \\.\pipe\testpipe
All pipe instances are busy.

Right after that, the "main" program says that the pipe is broken.

Confusingly, the exactly same program works correctly on Windows 7: The text "Hallo world!" can be read from the pipe in the terminal (with the exactly same command as above) just fine:

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. Alle Rechte vorbehalten.

C:\Users\testuser>type \\.\pipe\testpipe
Hallo!

What am I missing ???

Thanks!


Background:

My goal is to pass a string (password) to a command-line application, which is not able to take the string directly from the command-line. Instead, the command-line program can only take a file name and will read the string from the specified file. But I don't want to create a (temporary) "physical" file, but rather want to pass the string via named pipe - in a similar way I'd do it with mkfifo on Unix.

(I can not change the command-line program)

like image 733
MuldeR Avatar asked Sep 04 '18 19:09

MuldeR


1 Answers

(This post is VERY long to cover many aspects but the simple answer is covered in the first paragraph.)

Not a guaranteed answer but tests (shown later) indicate that

type \\.\pipe\testpipe

is opening the pipe twice and failing on the second open since the instance created in class Program has now been used. CreateFile allows opening a file (inc. devices such as pipes) with a desired access of 0 (not read or write) to obtain certain metadata, including perhaps if it even exists. If type is doing this then it cannot be used to access a (single instance) named pipe. Perhaps the behaviour of type changed from Win7 to Win10. (I'll run the following on Win7 one day.)

Proof (well seems like it):

using System;
using System.IO;
using System.IO.Pipes;
using System.Threading;

class makepipes
{
    const int   numserv = 8;

    static void Main(string[] args)
    {
    int i;
    Thread[] servers = new Thread[numserv];

    Console.Write("Waiting for client connection... ");
        for (i = 0; i < numserv; i++)
        {
            servers[i] = new Thread(ServerThread);
            servers[i].Start();
        }
        Thread.Sleep(250);
        while (i > 0)
        {
            for (int j = 0; j < numserv; j++)
            {
                if (servers[j] != null)
                {
                    if (servers[j].Join(250))
                    {
                        Console.WriteLine("Server thread[{0}] finished.", servers[j].ManagedThreadId);
                        servers[j] = null;
                        i--;    // decrement the thread watch count
                    }
                }
            }
        }
        Console.WriteLine("\nServer threads exhausted, exiting.");
    }

    private static void ServerThread(object data)
    {
        NamedPipeServerStream pipeServer =
            new NamedPipeServerStream("testpipe", PipeDirection.Out, numserv);

        int threadId = Thread.CurrentThread.ManagedThreadId;

    Console.WriteLine("NamedPipeServerStream object created in {0}.", threadId);
        // Wait for a client to connect
        pipeServer.WaitForConnection();

        Console.WriteLine("Client connected on thread[{0}].", threadId);
            try
            {
                using (StreamWriter sw = new StreamWriter(pipeServer))
                {
                    sw.AutoFlush = true;
                    sw.WriteLine("Hallo world from {0}!", threadId);
                    Console.WriteLine("Data was written from {0}.", threadId);
                }
            }
            catch (IOException e)
            {
                Console.WriteLine("Thread {2} failed {0}: {1}", e.GetType().Name, e.Message, threadId);
            }
            Console.WriteLine("Pipe closed in {0}.", threadId);
    }
}

Giving the following results (note: must use type in cmd.exe as PowerShell "type", alias for Get-Content, uses a FileStream object which cannot directly access devices or pipes. see very bottom.) Server output bounded by S#...#S, client by C#...#C

S#######
Servers> makepipes
Waiting for client connection... NamedPipeServerStream object created in 3.
NamedPipeServerStream object created in 6.
NamedPipeServerStream object created in 8.
NamedPipeServerStream object created in 10.
NamedPipeServerStream object created in 7.
NamedPipeServerStream object created in 5.
NamedPipeServerStream object created in 9.
NamedPipeServerStream object created in 4.
#######S

C#######
Client>type \\.\pipe\testpipe
#######C

S#######
Client connected on thread[3].
Client connected on thread[10].
Data was written from 10.
Pipe closed in 10.
Server thread[10] finished.
Thread 3 failed IOException: Pipe is broken.
Pipe closed in 3.
Server thread[3] finished.
#######S

C#######
Hallo world from 10!

Client>type \\.\pipe\testpipe
#######C

S#######
Client connected on thread[8].
Client connected on thread[5].
Data was written from 5.
Pipe closed in 5.
Thread 8 failed IOException: Pipe is broken.
Pipe closed in 8.
Server thread[5] finished.
Server thread[8] finished.
#######S

C#######
Hallo world from 5!

Client>type \\.\pipe\testpipe
#######C

S#######
Client connected on thread[9].
Client connected on thread[6].
Data was written from 6.
Pipe closed in 6.
Server thread[6] finished.
Thread 9 failed IOException: Pipe is broken.
Pipe closed in 9.
Server thread[9] finished.
#######S

C#######
Hallo world from 6!

Client>type \\.\pipe\testpipe
#######C

S#######
Client connected on thread[7].
Client connected on thread[4].
Data was written from 4.
Pipe closed in 4.
Thread 7 failed IOException: Pipe is broken.
Pipe closed in 7.
Server thread[7] finished.
Server thread[4] finished.

Server threads exhausted, exiting.
#######S

C#######
Hallo world from 4!

Client>type \\.\pipe\testpipe
The system cannot find the file specified.
#######C

Note the last response. The All pipe instances are busy. response implies that type is not closing the first open before attempting the second (or there are some really tight interprocess timing issues). (This looks like a job for ProcessMon!)

So this explains why type behaves as it does (maybe) but the OP seems to have only been using type for testing. The stated intention was that a command line application would read from a file name argument which is supplied the name of the pipe. So, as long as it only opens the pipe once, it should work. Some test examples,

S#######
Servers> makepipes
Waiting for client connection... NamedPipeServerStream object created in 5.
NamedPipeServerStream object created in 6.
NamedPipeServerStream object created in 4.
NamedPipeServerStream object created in 7.
NamedPipeServerStream object created in 10.
NamedPipeServerStream object created in 3.
NamedPipeServerStream object created in 8.
NamedPipeServerStream object created in 9.
#######S

C#######
Client>more < \\.\pipe\testpipe
#######C

S#######
Client connected on thread[5].
Data was written from 5.
Pipe closed in 5.
Server thread[5] finished.
#######S

C#######
Hallo world from 5!

Client>more \\.\pipe\testpipe
#######C

S#######
Client connected on thread[7].
Data was written from 7.
Pipe closed in 7.
Server thread[7] finished.
#######S

C#######
Hallo world from 7!

Client>sort \\.\pipe\testpipe
#######C

S#######
Client connected on thread[6].
Data was written from 6.
Pipe closed in 6.
Server thread[6] finished.
#######S

C#######
Hallo world from 6!

Client>findstr world \\.\pipe\testpipe
#######C

S#######
Client connected on thread[4].
Client connected on thread[10].
Thread 10 failed IOException: Pipe is broken.
Pipe closed in 10.
Thread 4 failed IOException: Pipe is broken.
Pipe closed in 4.
Server thread[10] finished.
Server thread[4] finished.
#######S

C####### (no output from findstr)

Client>find "world" \\.\pipe\testpipe

---------- \\.\PIPE\TESTPIPE
#######C

S#######
Client connected on thread[3].
Data was written from 3.
Pipe closed in 3.
Server thread[3] finished.
#######S

C#######
Hallo world from 3!
Unable to read file

Client>copy \\.\pipe\testpipe con:
#######C

S#######
Client connected on thread[9].
Thread 9 failed IOException: Pipe is broken.
Pipe closed in 9.
Server thread[9] finished.
Client connected on thread[8].
Data was written from 8.
Pipe closed in 8.
Server thread[8] finished.

Server threads exhausted, exiting.
#######S

C#######
Hallo world from 8!
The pipe has been ended.
        0 file(s) copied.
#######C

So, both findstr and copy double open. Also, for copy \\.\pipe\testpipe pipeit the response is 1 file(s) copied. (after 4 opens with 3 fails) but pipeit is empty. find appears to work but does not correctly process the closing of the write end of the pipe. Note how more < \\.\pipe\testpipe works correctly. This would imply (and has been tested as true) that any command line application that reads StdIn can be connected to a named pipe (using cmd.exe). Even findstr world < \\.\pipe\testpipe works and find "world" < \\.\pipe\testpipe doesn't complain. Redirection in cmd probably also works for StdOut and StdErr to a reading pipe server (or servers!) but haven't tested this (comments anyone?)

Final confirmation,

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

public class oneopen {
    [DllImport("Kernel32.dll",SetLastError=true)]
    extern static IntPtr CreateFile(
        string  lpFileName,
        int dwDesiredAccess,
        int dwShareMode,
        IntPtr  lpSecurityAttributes,
        int dwCreationDisposition,
        int dwFlagsAndAttributes,
        IntPtr  hTemplateFile
    );

    [DllImport("Kernel32.dll",SetLastError=true)]
    static extern bool ReadFile(
        IntPtr handle,
        byte[] bytes,
        int numBytesToRead,
        out int numBytesRead,
        IntPtr overlapped_MustBeZero
    );

    [DllImport("Kernel32.dll",SetLastError=true)]
    static extern bool CloseHandle(IntPtr handle);

    public static int Main(string[] args)
    {
        IntPtr  fh, fh2;
        byte[]  bytes = new byte[1024];
        int red;

        if (args.Length > 0)
        {
            if (args.Length > 1)
            {
                fh2 = CreateFile(args[0], 1, 0, IntPtr.Zero, 3, 0, IntPtr.Zero);
                Console.WriteLine("{0} {1}\n", fh2.ToInt64(), Marshal.GetLastWin32Error());
                if (args.Length > 2)
                {
                    CloseHandle(fh2); // Don't care if fh == INVALID_HANDLE_VALUE
                    Thread.Sleep(Int32.Parse(args[2]));
                }
            }
            fh = CreateFile(args[0], 1, 3, IntPtr.Zero, 3, 0, IntPtr.Zero);
            Console.WriteLine("{0} {1}\n", fh.ToInt64(), Marshal.GetLastWin32Error());
        
            while (ReadFile(fh, bytes, bytes.Length, out red, IntPtr.Zero) && red > 0)
                Console.WriteLine(Encoding.ASCII.GetString(bytes, 0, red));
        }
        return Marshal.GetLastWin32Error();
    }
}

This allows testing of a single open on the pipe, an open/close/(delay)/open and two simultaneous opens. Various testing showed the anticipated results with the one relevant to the OP shown (using original single instance pipe)

S#######
Server> makepipe
NamedPipeServerStream object created.
Waiting for client connection...#######S

C#######
Client> oneopen \\.\pipe\testpipe
#######C

                        S#######Client connected.
Data was written.
Pipe closed.
#######S

C#######
604 0

Hallo world!
#######C

I know this is a bit TLDR; but I wanted to cover many variations.

Finally, regarding PowerShell as promised, the following test results were obtained

S#######
PS Server> .\makepipes
Waiting for client connection... NamedPipeServerStream object created in 6.
NamedPipeServerStream object created in 7.
NamedPipeServerStream object created in 5.
NamedPipeServerStream object created in 8.
NamedPipeServerStream object created in 9.
NamedPipeServerStream object created in 10.
NamedPipeServerStream object created in 4.
NamedPipeServerStream object created in 3.
#######S

C#######
PS Client> type \\.\pipe\testpipe
#######C

S#######
Client connected on thread[6].
Client connected on thread[7].
Client connected on thread[10].
Client connected on thread[8].
Thread 8 failed IOException: Pipe is broken.
Pipe closed in 8.
Thread 10 failed IOException: Pipe is broken.
Pipe closed in 10.
Thread 6 failed IOException: Pipe is broken.
Pipe closed in 6.
Thread 7 failed IOException: Pipe is broken.
Pipe closed in 7.
Server thread[10] finished.
Server thread[6] finished.
Server thread[7] finished.
Server thread[8] finished.
#######S

C#######
type : FileStream was asked to open a device that was not a file. For support for devices like 'com1:' or 'lpt1:',
call CreateFile, then use the FileStream constructors that take an OS handle as an IntPtr.
At line:1 char:1
+ type \\.\pipe\testpipe
+ ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-Content], NotSupportedException
    + FullyQualifiedErrorId : System.NotSupportedException,Microsoft.PowerShell.Commands.GetContentCommand

PS Client> type \\.\pipe\testpipe
#######C

S#######
Client connected on thread[9].
Client connected on thread[3].
Thread 9 failed IOException: Pipe is broken.
Pipe closed in 9.
Server thread[9] finished.
Client connected on thread[4].
Thread 3 failed IOException: Pipe is broken.
Pipe closed in 3.
Client connected on thread[5].
Thread 5 failed IOException: Pipe is broken.
Pipe closed in 5.
Server thread[3] finished.
Thread 4 failed IOException: Pipe is broken.
Pipe closed in 4.
Server thread[4] finished.
Server thread[5] finished.

Server threads exhausted, exiting.
#######S

C#######
type : FileStream was asked to open a device that was not a file. For support for devices like 'com1:' or 'lpt1:',
call CreateFile, then use the FileStream constructors that take an OS handle as an IntPtr.
At line:1 char:1
+ type \\.\pipe\testpipe
+ ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-Content], NotSupportedException
    + FullyQualifiedErrorId : System.NotSupportedException,Microsoft.PowerShell.Commands.GetContentCommand

PS Client> type \\.\pipe\testpipe
type : Cannot find path '\\.\pipe\testpipe' because it does not exist.
At line:1 char:1
+ type \\.\pipe\testpipe
+ ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (\\.\pipe\testpipe:String) [Get-Content], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
#######C

So, not only does PowerShell not like getting content from pipes but it opens (and closes) it 4 times before deciding this!!

like image 188
Uber Kluger Avatar answered Nov 05 '22 09:11

Uber Kluger