0

I'd like to spawn a child window from a parent powershell window, and the child window would load a .ps1 file with a do/while loop in it that referenced a parameter that could be dynamically updated.

So I could call the child window into existence from the parent window with the names parameter and then call it again with a new set of names dynamically at any point, or somehow update the variable dynamically for the child window.

Are either of these possible with Powershell? Can I have the variable scope encompass the parent and child Powershell windows? Can I update a child windows variable array dynamically? Is my only avenue writing to a text file and having the child window code constantly poll that text file? If so what's the best way to avoid a permissions lock issue?

#parentwindow

$handle = . \childWindow.ps1 -names @{"One", "Two", "Three", "Four", "Five"}


#childwindow

Param ($names)  

do
{
    foreach ($name in $names)
    {
        Write-Host $name;
    }
    sleep 5;

}while ($continueProcessing)

a few minutes later

$handle = . \childWindow.ps1 -names @{"Six", "Seven", "Eight", "Nine", "Ten"}

or something akin to

$handle.names = @{"Six", "Seven", "Eight", "Nine", "Ten"}

enter image description here

Bbb
  • 517
  • 6
  • 27
  • 2
    This seems like a solution looking for a problem. What exactly are you trying to do? Maybe there is a better way that avoids multiple processes, etc. – boxdog May 10 '21 at 15:27
  • I'm not sure about updating variables once the instance is launched, but to launch the new instance I think you are looking for: `invoke-expression 'cmd /c start powershell -file "childWindow.ps1 -names @{}" '` from the parent window – Jeramy May 10 '21 at 15:37
  • you may want to look at synchronized collections >>> Thread-Safe Collections | Microsoft Docs — https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/?redirectedfrom=MSDN – Lee_Dailey May 10 '21 at 15:40
  • @boxdog lol I'm trying to share variables between parent and child Powershell windows dynamically after starting the child from the parent similarly to the way Jeramy wrote – Bbb May 10 '21 at 16:21
  • if you want to have IPC between various PowerShell process, it's possible ([this way for example](https://stackoverflow.com/a/66640388/3641635)), but do you really need that in a full PowerShell environment ? What do you want to achieve ? – Zilog80 May 10 '21 at 16:23
  • @Lee_Dailey this is what I'm trying to do, if I understand concurrent collections I've implemented it correctly for this test ... is this what you were suggesting? (see image in first post) – Bbb May 10 '21 at 17:11
  • @Zilog80 named pipes appears to be what I need, I'm having a hard time implementing it though. It continually hangs when I try and do anything other than send a single message ... maybe that's by design? – Bbb May 10 '21 at 20:22
  • @Bbb - i can't read the images of code you posted. [*sigh ...*] take a look at this post about why doing that is disrecommended ... Why not upload images of code/errors when asking a question? - Meta Stack Overflow — https://meta.stackoverflow.com/questions/285551/why-not-upload-images-of-code-errors-when-asking-a-question – Lee_Dailey May 11 '21 at 06:09
  • @Bbb I get the technique you are trying to implement, but _why?_ Let's say you get this working, what tasks will you be performing with this setup that you can't do using some easier method? – boxdog May 11 '21 at 07:08
  • @Bbb I have refined the [example implementation](https://stackoverflow.com/a/66640388/3641635) of named pipe with powershell, more readable and should not hang on multiple msg. Anyway, you should specify more precisely your goal, as with full powershell you should not need that... – Zilog80 May 11 '21 at 12:06
  • @Bbb There was a miss in the reconnection (must be on finally clause), edit updated... – Zilog80 May 11 '21 at 12:19
  • @Bbb And i forgot two NOP. Edit done. – Zilog80 May 11 '21 at 12:28
  • @boxdog I'm open to suggestions outside of a temp file intermediary. Lee_Dailey, I haven't gotten errors, the named pipes will freeze and I'll have to end task. If named pipes is a bad idea, I'm open to other suggestions. It seemed like a great solution when recommended. – Bbb May 11 '21 at 17:44
  • @Bbb If you're using only powershell scripts, you can use [directly named pipe between them like described here](https://stackoverflow.com/questions/32056955/pipelining-between-two-separate-powershell-processes). – Zilog80 May 11 '21 at 17:54
  • @Zilog80 my end goal is to have a parent ps1 spawn a child ps1 (separate window) that processes functions based on parameters. I'd like to be able to send different commands from the parent to the child window to update the parameters or to exit and close the child window. If this isn't clear, I can try to make a flow chart if that would help. I'm going to look at your updated example now. – Bbb May 11 '21 at 18:09
  • What you are asking for is clear, it's your intent behind that would be useful (what theses ps scripts will do ?). It seems anyway that you really need IPC. As you are only using PowerShell scripts, you can do IPC through [named pipe](https://stackoverflow.com/questions/32056955/pipelining-between-two-separate-powershell-processes) but many [other ways](https://learn.microsoft.com/en-us/windows/win32/ipc/interprocess-communications) are possible with PowerShell. Named pipe and sockets are relatively easy to understand and use, and generally reliable when well implemented. – Zilog80 May 11 '21 at 18:18
  • @Zilog80 I've been working with named pipes for the past 2 days and the blocking feature is making it too difficult, even trying to implement a sender and receiver into a runspace pool for both the parent and the client. I would like to try something else, it appears that tcp connections are also blocking. I really need something asynchronous that can poll the open the connection and poll it routinely and move on. Any suggestions? I'm trying to send variable values to and receive return values back. Example: to send a list of servers upon initialization and then add servers midway through. – Bbb May 13 '21 at 14:54
  • @Bbb I hope the answer will help you finding your blocking operations. – Zilog80 May 14 '21 at 22:08
  • @Bbb If you have to implement asynchronous exchanges in a 'premise-style' context (the _'process'_ block of a PowerShell cmdlet/function), you can find a workaround to do that in [these answer](https://stackoverflow.com/a/67724572/3641635). – Zilog80 Jun 02 '21 at 15:14

1 Answers1

1

As OP beg for that, here is simple and very basic full PowerShell implementation of a named pipe client / server that will not hang. Launch the server script in one CLI. Then launch the client script in another CLI, you can launch that last one many times and see the effect on the server CLI.

The PowerShell code is not very clean, but it's just an example demonstration. And i can't afford hours on that.

The server script is a basic "host list" maintainer, just note that, due to the use of a StreamWriter, the output pipe requires to be re-instantiated after each write.

This server understand theses commands :

  • hello, it replies with current date and time
  • add [host], it adds [host] to its internal list and reply with the current list size.
  • del [host], it removes [host] from its internal list and reply with the remaining number of hosts.
  • list, it reply with the list of [host] in its internal list.

Server script

# NamedPipe Server script
$status = 0;
$npipeServerInput = new-object System.IO.Pipes.NamedPipeServerStream('my_pipe_server_input', [System.IO.Pipes.PipeDirection]::In, 1, [System.IO.Pipes.PipeTransmissionMode]::Message, [System.IO.Pipes.PipeOptions]::None, 256, 256);
if (!$npipeServerInput) {
  Write-Host("Failed to open server input pipe...");
  exit(1);
}
$npipeServerOutput = new-object System.IO.Pipes.NamedPipeServerStream('my_pipe_server_output', [System.IO.Pipes.PipeDirection]::Out, 1, [System.IO.Pipes.PipeTransmissionMode]::Message, [System.IO.Pipes.PipeOptions]::None, 256, 256);
if (!$npipeServerOutput) {
  Write-Host("Failed to open server output pipe...");
  $npipeServerInput.Dispose();
  exit(1);
}
$server_list = new-object System.Collections.ArrayList;
function ProcessMessage([String]$ParMessage) {
  $reply = ""
  switch($ParMessage) {
    "hello" {
      $curdte = Get-Date -Format 'HH:mm:ss';
      $reply='Reply to '+$ParMessage+', im here, time is '+$curdte;
    }
    "list" {
      $server_list | %{ $reply += [Environment]::NewLine + $_; };
    }
    default {
      if ($ParMessage.StartsWith("add ")) {
        $reply = $server_list.Add($ParMessage.Split(" ")[1]);
        $reply = "Added, " + [String]$server_list.Count + " items";
      } else {
        if ($ParMessage.StartsWith("del ")) {
          $reply = $server_list.Remove($ParMessage.Split(" ")[1]);
          $reply = "Removed, " + [String]$server_list.Count + " items left";
        } else {
          $reply = "Unknown command " + $ParMessage;
        }
      }
    }
  }
  return($reply);
}
Try {
  $npipeServerInput.WaitForConnection();
  $pipeReader = new-object System.IO.StreamReader($npipeServerInput);
  if (!$pipeReader) {
    Write-Host("Failed to read server input pipe...");
    $status = 2;
  } else {
    Try {
      While(($msg = $pipeReader.ReadLine()) -notmatch 'QUIT') {
        Try {
          If ($msg.Length -gt 0) {
            $disp='Server got :'+$msg;
            Write-Host($disp);
            $reply = ProcessMessage($msg);
            $npipeServerOutput.WaitForConnection();
            $pipeWriter = new-object System.IO.StreamWriter($npipeServerOutput);
            if (!$pipeWriter) {
              Write-Host("Failed to write server output pipe...");
              $status = 2;
            } else {
              Try {
                $reply_flag=0;
                While($reply_flag -eq 0) {
                  Try {
                    # Write-Output($reply) ^>5;
                    $pipeWriter.WriteLine($reply);
                    $reply_flag=1;
                  } Catch [System.IO.IOException] {
                    # Deal as you whish with potential errors
                    Write-Host("Failed to write to server output pipe...");
                    exit(3);
                  };
                };
                $pipeWriter.Flush();
                Write-Host('Reply sent. '+$reply);
              } Finally {
                # The streamwriter disposal will mess up our output pipe, so we recrerate it...
                $pipeWriter.Dispose();
                $npipeServerOutput.Dispose();
                $npipeServerOutput = new-object System.IO.Pipes.NamedPipeServerStream('my_pipe_server_output', [System.IO.Pipes.PipeDirection]::Out, 1, [System.IO.Pipes.PipeTransmissionMode]::Message, [System.IO.Pipes.PipeOptions]::None, 256, 256);
              }
            }
          }
        } Finally {
          $npipeServerInput.Disconnect();
          $npipeServerInput.WaitForConnection();
        }
      };
    } Catch [System.IO.IOException] {
      # Deal as you whish with potential errors, you will have InvalidOperation here when the streamReader is empty
      Write-Host("Failed to read server input pipe...");
      exit(4);
    }
  }
} Catch [System.IO.IOException] {
  Write-Host("I/O failure...");
} Finally {
  If ($npipeServerInput) {
    $npipeServerInput.Dispose();
  }
  If ($npipeServerOutput) {
    $npipeServerInput.Dispose();
  }
}
exit($status)

The client script :

  1. Sends hello to the server and display its reply.
  2. Sends add localhost to the server and display its reply.
  3. Sends add google.com to the server and display its reply.
  4. Sends add stackoverflow.com to the server and display its reply.
  5. Sends del google.com to the server and display its reply.
  6. Sends list to the server and display its reply.

The client script wait 5 seconds for a reply for each command sent, if the server hasn't replied in the delay the client script aborts. Each time the client script run, localhost and stackoverflow.com are added to the server list.

For commodity, i shamelessly take the New-ScriptBlockCallback from Oisin Grehan, very convenient to make your callback directly in PowerShell.

As the server script re-instantiated its output pipe after each write, the client script do then the same for its connected input pipe. This can be avoided by using something else in the server script than the StreamWriter (i keep it to show that with PowerShell, you have to care about underlying disposal of objects). The pipe methods are static methods in a class for convenience, as i prefer a return that doesn't take everything that happens in the functions. There is also a basic stack for incoming messages from the server, to show a basic way to handle incoming messages with a callback.

Client script

# NamedPipe Client script
#
# Client return status
#
$status = 0;
#
# Input and output named pipes to exchange with the server
# 
$npipeClientInput = new-object System.IO.Pipes.NamedPipeClientStream('.', 'my_pipe_server_output', [System.IO.Pipes.PipeDirection]::In, [System.IO.Pipes.PipeOptions]::None);
if (!$npipeClientInput) {
  Write-Host("Main: Failed to open client input pipe...");
  exit(1);
}
$npipeClientOutput = new-object System.IO.Pipes.NamedPipeClientStream('.', 'my_pipe_server_input', [System.IO.Pipes.PipeDirection]::Out, [System.IO.Pipes.PipeOptions]::None);
if (!$npipeClientOutput) {
  Write-Host("Main: Failed to open client output pipe...");
  $npipeClientInput.Dispose();
  exit(1);
}
# Thanks to Oisin Grehan, https://stackoverflow.com/a/41155285/3641635
function New-ScriptBlockCallback {
    param(
        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [scriptblock]$Callback
    )
<#
    .SYNOPSIS
        Allows running ScriptBlocks via .NET async callbacks.
 
    .DESCRIPTION
        Allows running ScriptBlocks via .NET async callbacks. Internally this is
        managed by converting .NET async callbacks into .NET events. This enables
        PowerShell 2.0 to run ScriptBlocks indirectly through Register-ObjectEvent.         
 
    .PARAMETER Callback
        Specify a ScriptBlock to be executed in response to the callback.
        Because the ScriptBlock is executed by the eventing subsystem, it only has
        access to global scope. Any additional arguments to this function will be
        passed as event MessageData.
         
    .EXAMPLE
        You wish to run a scriptblock in reponse to a callback. Here is the .NET
        method signature:
         
        void Bar(AsyncCallback handler, int blah)
         
        ps> [foo]::bar((New-ScriptBlockCallback { ... }), 42)                        
 
    .OUTPUTS
        A System.AsyncCallback delegate.
#>
    # is this type already defined?    
    if (-not ("CallbackEventBridge" -as [type])) {
        Add-Type @"
            using System;
             
            public sealed class CallbackEventBridge
            {
                public event AsyncCallback CallbackComplete = delegate { };
 
                private CallbackEventBridge() {}
 
                private void CallbackInternal(IAsyncResult result)
                {
                    CallbackComplete(result);
                }
 
                public AsyncCallback Callback
                {
                    get { return new AsyncCallback(CallbackInternal); }
                }
 
                public static CallbackEventBridge Create()
                {
                    return new CallbackEventBridge();
                }
            }
"@
    }
    $bridge = [callbackeventbridge]::create()
    Register-ObjectEvent -input $bridge -EventName callbackcomplete -action $callback -messagedata $args > $null
    $bridge.callback
}
class ClientPipes {
  #
  # Internal FIFO stack for incoming messages from the server
  # 
  static [System.Collections.Queue]$ReadStack;
  #
  # Function to send a message to the server
  #
  # Parameters : 
  #   ParOutputPipe : Ref to the output pipe
  #   $Message      : Message to send
  # Returns :
  #   0 : Success
  #   1 : Wrong parameters
  #   2 : I/O failure
  static [int32]SendServer([ref]$ParOutputPipe, [String]$Message) {
    $OutputPipe = [System.IO.Pipes.NamedPipeClientStream]$ParOutputPipe.value;
    # Connect the client output pipe
    if (!$OutputPipe.IsConnected) {
      $OutputPipe.Connect(2000);
      Write-Host("SendServer: output pipe connected.");
    }
    $pipeWriter = new-object System.IO.StreamWriter($OutputPipe);
    if (!$pipeWriter) {
      Write-Host("SendServer: Failed to set a writer to client output pipe...");
      $status = 2;
    } else {
      Try {
        if (!$pipeWriter) {
          Write-Host("SendServer: Wrong parameters..");
          return(1);
        }
        Try {
          # We send to the server
          $pipeWriter.WriteLine($Message);
          $pipeWriter.Flush();
          $OutputPipe.WaitForPipeDrain();
        } Catch [System.IO.IOException] {
          Write-Host("SendServer: Failed to write to server...");
          return(2);
        } Finally {
          # The streamwriter disposal will mess up our output pipe, so we recrerate it...
          $pipeWriter.Dispose();
          $OutputPipe.Dispose();
          $ParOutputPipe.value = new-object System.IO.Pipes.NamedPipeClientStream('.', 'my_pipe_server_input', [System.IO.Pipes.PipeDirection]::Out, [System.IO.Pipes.PipeOptions]::None);
        }
      } Catch [System.IO.IOException] {
        # Deal as you whish with potential errors, you will have InvalidOperation here when the streamReader is empty
        Write-Host("SendServer: Failed to write client output pipe...");
        return(2);
      }
    }
    return(0);
  }
  #
  # Synchonized function to push on the FIFO stack
  # 
  # Parameters : 
  #   $Message      : Message to push
  # Returns :
  #   0 : Success
  #   1 : Synchronization failure
  static [int32]PushOnStack([System.Collections.Queue]$Stack, [string]$ParMessage) {
    $SyncStack = [System.Collections.Queue]::Synchronized($Stack);
    if (!$SyncStack) {
      Write-Host("PushOnStack: Failed to synchronize...");
      return(1);
    }
    $SyncStack.Enqueue($ParMessage);
    return(0);
  }
  #
  # Synchonized function to pop from the FIFO stack
  # 
  # Parameters : 
  #   $Message      : Buffer to store message
  # Returns :
  #   0 : Success, there was a message
  #   1 : Success, there was no message
  #   2 : Synchronization failure
  static [int32]PopFromStack([System.Collections.Queue]$Stack,[ref]$ParMessage) {
    $SyncStack = [System.Collections.Queue]::Synchronized($Stack);
    if (!$SyncStack) {
      Write-Host("PopFromStack: Failed to synchronize...");
      return(2);
    }
    if ($SyncStack.Count -ne 0) {
      $ParMessage.value = [String]$SyncStack.Dequeue();
      return(0);
    } else {
      return(1);
    }
  }
  #
  # Callback function for incoming messages from the server
  #
  # Parameters : 
  #   $Result       : Object issued
  # Returns :
  #   0 : Success
  #   1 : Failure
  #   2 : Failure, stack
  #   3 : Failure, pipe
  static [int32]ProcessRead([IAsyncResult]$Result) {
    $status = 0;
    # Check if that comes from our ReadServer function
    $RefPipe = [ref]$Result.AsyncState[0];
    $Pipe = [System.IO.Pipes.NamedPipeClientStream]$RefPipe.value;
    if (!$Pipe) {
      Write-Host("ProcessRead: Unexpected event...");
      $status = 1;
    } else {
      Write-Host("ProcessRead: Callback event");
      $Msg = [System.Text.Encoding]::ASCII.GetString($Result.AsyncState[1]);
      $Stack = [System.Collections.Queue]$Result.AsyncState[2];
      Write-Host("ProcessRead: read " + $Msg);
      if ([ClientPipes]::PushOnStack($Stack, $Msg) -ne 0) {
        Write-Host("ProcessRead: Failed to push message...");
        $status = 2;
      }
      Try {
        $Pipe.EndRead($Result);
        #$Pipe.Close();
      } Catch [System.IO.IOException] {
        Write-Host("Failed to terminate read client input pipe...");
        $status = 3;
      } finally {
        # As the server have probably closed the pipe, we recreate it
        $RefPipe.value = new-object System.IO.Pipes.NamedPipeClientStream('.', 'my_pipe_server_output', [System.IO.Pipes.PipeDirection]::In, [System.IO.Pipes.PipeOptions]::None);
      }
    }
    return($status);
  }
  #
  # Function to set a handler to read incoming messages from the server
  #
  # Parameters : 
  #   $ParInputPipe  : Ref to input pipe
  # Returns :
  #   0 : Success
  #   1 : Wrong parameters
  #   2 : I/O failure
  #   3 : Internal failure
  static [int32]ReadServer([ref]$ParInputPipe) {
    $InputPipe = [System.IO.Pipes.NamedPipeClientStream]$ParInputPipe.value;
    if (!$InputPipe) {
      Write-Host("ReadServer: Wrong parameters..");
      return(1);
    }
    Try {
      # We create the Async callback to receive messages
      $Buffer = New-Object Byte[] 256
      $CallBackParams = @($ParInputPipe, $Buffer, [ClientPipes]::ReadStack)
      $CallBack = New-ScriptBlockCallback { param([IAsyncResult] $Result) Write-Host("Callback event"); [ClientPipes]::ProcessRead($Result); };
      if (!$CallBack) {
        Write-Host("ReadServer: Failed to create a  callback...");
        return(3);
      }
      # Connect the Pipe
      if (!$InputPipe.IsConnected) {
        $InputPipe.Connect(2000);
        Write-Host("ReadServer: Pipe connected.");
      }
      # We add a signal handler to get incoming Message
      $AsyncHandler = $InputPipe.BeginRead($Buffer, 0, 256, $CallBack, $CallBackParams)
    } Catch [System.IO.IOException] {
      Write-Host("ReadServer: Failed to set a callback...");
      return(2);
    }
    return(0);
  }
  #
  # Get last message from the msg stack
  #
  # Parameters : 
  #   $LastMsg  : Buffer to store last message
  #   $ParDelay : Timeout in second
  # Returns :
  #   0 : Success, there was a message
  #   1 : Timeout, no last message
  #   2 : Failure
  static [int32]GetLastMessage([ref]$LastMsg, [int32]$ParDelay) {
    $StackStatus = 1;
    $Timer = [Diagnostics.Stopwatch]::StartNew();
    While($StackStatus -eq 1 -and $Timer.Elapsed.TotalSeconds -lt $ParDelay) {
      Write-Host("GetLastMessage: Waiting for a reply...");
      $StackStatus = [ClientPipes]::PopFromStack([ClientPipes]::ReadStack, $LastMsg);
      if ($StackStatus -eq 1) { 
        sleep -m 1000
      }
    };
    if ($StackStatus -eq 2) {
      Write-Host("GetLastMessage: Error on incoming reply...");
      return(2);
    }
    return($StackStatus); 
  }
  #
  # Exchnage request / rsponse with the server
  #
  # Parameters : 
  #   $InputPipe  : Ref to client input pipe
  #   $OutputPipe : Ref to client output pipe
  #   $Request    : Requets to send to the server
  #   $Response   : Buffer to store server response
  #   $ParDelay   : Timeout in second
  # Returns :
  #   0 : Success, we have have a response
  #   1 : Timeout, no last message
  #   2 : Failure
  static [int32]Request([ref]$InputPipe, [ref]$OutputPipe, [String]$Request,[ref]$Response, [int32]$ParDelay) {
    # We send to the server and we expect to get a reply
    Write-Host("Request: Sending " + $Request);
    if ([ClientPipes]::SendServer($OutputPipe, $Request) -ne 0) {
      Write-Host("Request: Failed to send a message through client output pipe...");
      return(2);
    } else {
      # We poll the incoming stack for messages
      if ([ClientPipes]::ReadServer($InputPipe) -ne 0) {
        Write-Host("Request: Failed to set a callback...");
        return(2);
      }
      if ([ClientPipes]::GetLastMessage($Response, $ParDelay) -eq 0) {
        return(0);
      }
      return(1);
    }
  }
}
#
# Main
#
[ClientPipes]::ReadStack = new-object System.Collections.Queue;
if (![ClientPipes]::ReadStack) {
  Write-Host("Main: Can't get a stack...");
  exit(1);
}
Try {
  Write-Host("Main: Setting up client");
  # We send to the server "Hello" and we expect to get a reply in less than 5 seconds
  Write-Host("Main: Sending hello");
  $LastMsg=""
  $status = [ClientPipes]::Request([ref]$npipeClientInput,[ref]$npipeClientOutput,"hello",[ref]$LastMsg, 5);
  if ($status -eq 0) {
    Write-Host("Main: Get a reply : " + $LastMsg);
  } else {
    exit(1);
  }
  # We send to the server  a request to add "localhsot" to its internal stack and we expect to get a reply in less than 5 seconds
  Write-Host("Main: Server, store localhost please.");
  $LastMsg=""
  $status = [ClientPipes]::Request([ref]$npipeClientInput,[ref]$npipeClientOutput,"add localhost",[ref]$LastMsg, 5);
  if ($status -eq 0) {
    Write-Host("Main: Get a reply : " + $LastMsg);
  } else {
    exit(1);
  }
  # We send to the server  a request to add "google.com" to its internal stack and we expect to get a reply in less than 5 seconds
  Write-Host("Main: Server, store google.com please.");
  $LastMsg=""
  $status = [ClientPipes]::Request([ref]$npipeClientInput,[ref]$npipeClientOutput,"add google.com",[ref]$LastMsg, 5);
  if ($status -eq 0) {
    Write-Host("Main: Get a reply : " + $LastMsg);
  } else {
    exit(1);
  }
  # We send to the server  a request to add "stackoverflow.com" to its internal stack and we expect to get a reply in less than 5 seconds
  Write-Host("Main: Server, store stackoverflow.com please.");
  $LastMsg=""
  $status = [ClientPipes]::Request([ref]$npipeClientInput,[ref]$npipeClientOutput,"add stackoverflow.com",[ref]$LastMsg, 5);
  if ($status -eq 0) {
    Write-Host("Main: Get a reply : " + $LastMsg);
  } else {
    exit(1);
  }
  # We send to the server  a request to remove "google.com" from its internal stack and we expect to get a reply in less than 5 seconds
  Write-Host("Main: Server, remove google.com please.");
  $LastMsg=""
  $status = [ClientPipes]::Request([ref]$npipeClientInput,[ref]$npipeClientOutput,"del google.com",[ref]$LastMsg, 5);
  if ($status -eq 0) {
    Write-Host("Main: Get a reply : " + $LastMsg);
  } else {
    exit(1);
  }
  # We send to the server an output of  its internal stack and we expect to get a reply in less than 5 seconds
  Write-Host("Main: Server, your stack please.");
  $LastMsg=""
  $status = [ClientPipes]::Request([ref]$npipeClientInput,[ref]$npipeClientOutput,"list",[ref]$LastMsg, 5);
  if ($status -eq 0) {
    Write-Host("Main: Get a reply : " + $LastMsg);
  } else {
    exit(1);
  }
} Catch [System.IO.IOException] {
  Write-Host("Main: I/O failure...");
} Finally {
  If ($npipeClientInput) {
    $npipeClientInput.Dispose();
  }
  If ($npipeClientOutput) {
    $npipeClientOutput.Dispose();
  }
}
exit($status)

On a final note, i guess that your troubles with pipes and sockets come from common things like :

  • "Non class" functions in PowerShell will returns everything from their stdout. As an example, the ArrayList.Add method in the server script output the resulting number of items on stdout, and then make the reply as an object instead of a String. A simple way to avoid that, assign its output to a variable.
  • Redirection like >NUL in 'read/write pipe' functions will mess up your pipes. Do not do that.
  • Some stream helper like StreamReader or StreamWriter will close the underlying pipe at their disposal, so take care of that or do not use them.

Surely i forgot plenty of things.

For the demo, the output of the server script :

Server got :hello
Reply sent. Reply to hello, im here, time is 23:23:13
Server got :add localhost
Reply sent. Added, 1 items
Server got :add google.com
Reply sent. Added, 2 items
Server got :add stackoverflow.com
Reply sent. Added, 3 items
Server got :del google.com
Reply sent. Removed, 2 items left
Server got :list
Reply sent.
localhost
stackoverflow.com

The output of the client script :

Main: Setting up client
Main: Sending hello
Request: Sending hello
SendServer: output pipe connected.
ReadServer: Pipe connected.
Callback event
ProcessRead: Callback event
ProcessRead: read Reply to hello, im here, time is 23:23:13

GetLastMessage: Waiting for a reply...
Main: Get a reply : Reply to hello, im here, time is 23:23:13

Main: Server, store localhost please.
Request: Sending add localhost
SendServer: output pipe connected.
ReadServer: Pipe connected.
GetLastMessage: Waiting for a reply...
Callback event
ProcessRead: Callback event
ProcessRead: read Added, 1 items

GetLastMessage: Waiting for a reply...
Main: Get a reply : Added, 1 items

Main: Server, store google.com please.
Request: Sending add google.com
SendServer: output pipe connected.
ReadServer: Pipe connected.
Callback event
ProcessRead: Callback event
ProcessRead: read Added, 2 items

GetLastMessage: Waiting for a reply...
Main: Get a reply : Added, 2 items

Main: Server, store stackoverflow.com please.
Request: Sending add stackoverflow.com
SendServer: output pipe connected.
ReadServer: Pipe connected.
Callback event
ProcessRead: Callback event
ProcessRead: read Added, 3 items

GetLastMessage: Waiting for a reply...
Main: Get a reply : Added, 3 items

Main: Server, remove google.com please.
Request: Sending del google.com
SendServer: output pipe connected.
ReadServer: Pipe connected.
GetLastMessage: Waiting for a reply...
Callback event
ProcessRead: Callback event
ProcessRead: read Removed, 2 items left

GetLastMessage: Waiting for a reply...
Main: Get a reply : Removed, 2 items left

Main: Server, your stack please.
Request: Sending list
SendServer: output pipe connected.
ReadServer: Pipe connected.
GetLastMessage: Waiting for a reply...
Callback event
ProcessRead: Callback event
ProcessRead: read
localhost
stackoverflow.com

GetLastMessage: Waiting for a reply...
Main: Get a reply :
localhost
stackoverflow.com

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Zilog80
  • 2,534
  • 2
  • 15
  • 20