0

I have a simple Powershell script that I wrote in the Powershell ISE. The gist of it is that it watches a named pipe for a write as a signal to perform an action, while at the same time monitoring its boss process. When the boss-process exits, the script exits as well. Simple.

After struggling to get the named pipe working in Powershell without crashing, I managed to get working code, which is shown below. However, while this functions great in the Powershell ISE and interactive terminals, I've been hopeless in getting this to work as a standalone script.

$bosspid = 16320

# Create the named pipe
$pipe = new-object System.IO.Pipes.NamedPipeServerStream(
    -join('named-pipe-',$bosspid),
    [System.IO.Pipes.PipeDirection]::InOut,
    1,
    [System.IO.Pipes.PipeTransmissionMode]::Byte,
    [System.IO.Pipes.PipeOptions]::Asynchronous
)

# If we don't do it this way, Powershell crashes
# Brazenly stolen from github.com/Tadas/PSNamedPipes
Add-Type @"
    using System;
    public sealed class CallbackEventBridge
    {
        public event AsyncCallback CallbackComplete = delegate {};
        private void CallbackInternal(IAsyncResult result)
        {
        CallbackComplete(result);
        }
        public AsyncCallback Callback
        {
        get { return new AsyncCallback(CallbackInternal); }
        }
    }
"@

$cbbridge = New-Object CallBackEventBridge
Register-ObjectEvent -InputObject $cbbridge -EventName CallBackComplete -Action {
        param($asyncResult)
        $pipe.EndWaitForConnection($asyncResult)
        $pipe.Disconnect()
        $pipe.BeginWaitForConnection($cbbridge.Callback, 1)
        Host-Write('The named pipe has been written to!')
}

# Make sure to close when boss closes
$bossproc = Get-Process -pid $bosspid -ErrorAction SilentlyContinue
$exitsequence = {
    $pipe.Dispose()
    [Environment]::Exit(0)
}
if (-Not $bossproc) {$exitsequence.Invoke()}
Register-ObjectEvent $bossproc -EventName Exited -Action {$exitsequence.Invoke()}

# Begin watching for events until boss closes
$pipe.BeginWaitForConnection($cbbridge.Callback, 1)

The first problem is that the script terminates before doing anything meaningful. But delaying end of execution with such tricks like while($true) loops, the -NoExit flag, pause command, or even specific commands which seem made for the purpose, like Wait-Event, will cause the process to stay open, but still won't make it respond to the events.

detuur
  • 117
  • 1
  • 5
  • 1
    as a suggestion this article has a package you can get from nuget for named pipes. https://stackoverflow.com/questions/13806153/example-of-named-pipes Might be worth a look – Thom Schumacher Sep 14 '18 at 18:50
  • Much appreciated, but the situation doesn't allow for installing packages and I'd like to avoid building a 1000-line monstrosity for something that ought to be simple (i.e. I can't do a literal import of it into my code). – detuur Sep 14 '18 at 19:45
  • can you add a bit to show what your intent is. running your code in VSCODE works. it closes once the notepad I spawn closes: & notepad $bosspid = (get-process -name notepad).id – Thom Schumacher Sep 14 '18 at 20:12
  • The use case is convoluted: this script is embedded in a Lua script that serves as a plugin to the mpv media player. mpv scripts are widely distributed as single Lua files. Inside the Lua script, I run powershell with the -Command flag and this script. The Lua script is a "boss key" script, and I need the powershell script to minimize the player's window. I need it to listen on a named pipe because running powershell takes a second to start, so it needs to be running already, and named pipes are the only kind of IPC I can do between the two languages. – detuur Sep 14 '18 at 20:23
  • Personally I was just hoping that I had missed something obvious. Also note that it does indeed run just fine in interactive terminals such as ISE's or VSCode's. The trouble starts when saved as a .ps1 script and invoked as such. – detuur Sep 14 '18 at 20:25
  • there is another post about using the same CallBackEventBridge that might help a little here: https://stackoverflow.com/questions/19102154/powershell-asynccallback-events-using-hidlibrary – Thom Schumacher Sep 14 '18 at 21:41
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/180079/discussion-between-thom-schumacher-and-detuur). – Thom Schumacher Sep 14 '18 at 21:42

1 Answers1

0

I gave up on doing it the "proper" way and have instead reverted to using synchronous code wrapped in while-true blocks and Job control.

$bosspid = (get-process -name notepad).id

# Construct the named pipe's name
$pipename = -join('named-pipe-',$bosspid)
$fullpipename = -join("\\.\pipe\", $pipename) # fix SO highlighting: "

# This will run in a separate thread
$asyncloop = {
    param($pipename, $bosspid)
    # Create the named pipe
    $pipe = new-object System.IO.Pipes.NamedPipeServerStream($pipename)

    # The core loop
    while($true) {
        $pipe.WaitForConnection()
        # The specific signal I'm using to let the loop continue is
        # echo m > %pipename%
        # in CMD. Powershell's echo will *not* work. Anything other than m
        # will trigger the exit condition.
        if ($pipe.ReadByte() -ne 109) {
            break 
        }
        $pipe.Disconnect()
        # (The action this loop is supposed to perform on trigger goes here)
    }
    $pipe.Dispose()
}

# Set up the exit sequence
$bossproc = Get-Process -pid $bosspid -ErrorAction SilentlyContinue
$exitsequence = {
    # While PS's echo doesn't work for passing messages, it does
    # open and close the pipe which is enough to trigger the exit condition.
    &{echo q > $fullpipename} 2> $null 
    [Environment]::Exit(0)
}
if ((-Not $bossproc) -or $bossproc.HasExited) { $exitsequence.Invoke() }

# Begin watching for events until boss closes
Start-Job -ScriptBlock $asyncloop -Name "miniloop" -ArgumentList $pipename,$bosspid
while($true) {
    Start-Sleep 1
    if ($bossproc.HasExited) { $exitsequence.Invoke() }
}

This code works just fine now and does the job I need.

detuur
  • 117
  • 1
  • 5