Win32::PowerShell::IPC - Set up IPC between Perl and a PowerShell child process
version 0.02
my $ps= Win32::PowerShell::IPC->new(); # Set up MS Exchange remote session, which takes a dozen seconds $ps->run_or_die('$pw = "'.$pass.'" | ConvertTo-SecureString -AsPlainText -Force'); $ps->run_or_die('$credential = New-Object System.Management.Automation.PSCredential' .' -ArgumentList "'.$username.'", $pw'); $ps->run_or_die('$session = New-PSSession -ConfigurationName Microsoft.Exchange' .' -ConnectionUri https://ps.outlook.com/powershell' .' -Credential $credential -Authentication Basic -AllowRedirection'); $ps->run_or_die('Import-PSSession $session'); # Now run all sorts of methods without waiting again!
There's a lot of things in the Microsoft world that can't be done with perl. This is even more true with many Microsoft services offering PowerShell integration instead of more accessible Web APIs. While you can certainly write out a PowerShell script file and execute it, the session setup can be extremely slow and you might want to run numerous commands and take action based on the outcome. And you might rather do the logic in Perl than write all that in PowerShell, especially if it involves your database.
This module fires up a captive child PowerShell process to which you can submit commands, and receive the results. It's all text for now, but Perl excels at messy stuff like this.
PowerShell also seems to offer an option to exchange commands and results as XML, which would be a lot more reliable than text, but I haven't explored this yet. Patches welcome. (and good grief, haven't they discovered JSON over at Redmond, yet?)
This module is specific to Windows, and only tested on Strawberry perl so far. I was late to the party learning that PowerShell can run on Linux. On Linux, most of the problems solved by this module aren't problems, so you might as well just use IPC::Run. However, if I get around to implementing the XML communication protocol, I'll release another module for Linux.
Whether PowerShell is running
Hashref of options to pass to PowerShell.exe The default is { -ExecutionPolicy => 'RemoteSigned' } and there is also an implied { -Command => "-" } which is required for the piping to work.
{ -ExecutionPolicy => 'RemoteSigned' }
{ -Command => "-" }
Lazy-built from exe_options. You can override this if you want, but make sure to include "-Command -"
"-Command -"
Full path to PowerShell.exe, lazy-built on demand from $PATH if you don't specify it. This attribute is writeable, but won't have any effect once the child process is started.
The Win32::Process of PowerShell, initialized by "start_shell" or by calling any of the run/begin methods.
When this object is destroyed, wait up to this many milliseconds for the PowerShell process to exit. (we send it an "exit" command) Default is 2000. (2 seconds).
The write-side of PowerShell's STDIN pipe
The read-side of PowerShell's STDOUT pipe
The Windows Handle for the read handle of PowerShell's STDOUT pipe. The Windows Handle is needed for calling Win32 API calls via i.e. Win32::API wrappers, which can't accept a perl globref.
The accumulated read buffer from STDOUT of PowerShell. Used to hold leftover stream contents that might follow the end of a command output.
Standard Moo constructor. All attributes are optional. You might consider setting "cleanup_delay" or "exe_path"
Start the PowerShell process. Dies on any failure. Returns true.
Send "exit" command to shell, then wait up to "cleanup_delay" milliseconds for it to exit. If it doesn't, then kill it forcibly.
Note that terminate_shell is called when the object goes out of scope, or before global destruction at the END{} of the perl script.
$ps->begin_command("Text to execute");
This sends the text to the PowerShell instance, or dies trying. It does not wait for a result. It actually also sends an "echo" command that is used to detect the end of the output from your command.
my $output= $ps->collect_command;
This blocks until it receives the full output from your oldest pending command, and no other command. (you may have multiple pending commands) This module delimits the output with "echo" statements so that it can tell where the output of a command ends, but you shouldn't ever see signs of this implementation detail. I hope.
If you don't want to block, see "stdout_readable" and "read_more". Not that there's a complete solution there... but it will get you a little farther.
my $output= $ps->run_command("My Command;");
Send the command and then wait for the result. This is like "begin_command" + "collect_command" except that it also discards the output of any previous commands to make sure that you're getting the output from this command.
Like "run_command", but if the output looks like a PowerShell exception report, die instead of return.
Lower-level method to write all data to the PowerShell pipe, but also die if PowerShell isn't running.
Lower-level method to read more data from the pipe into "rbuf" but also die if PowerShell isn't running.
if ($ps->stdout_readable) { $ps->read_more; ... # now inspect $ps->rbuf }
Calls "PeekNamedPipe" on $ps->stdout_h file handle, and returns true if there are bytes available.
$ps->stdout_h
Win32::PowerShell::IPC::PeekNamedPipe( $win32_handle, $buffer, $buffer_size, $bytes_read, $bytes_available, $bytes_remaining_always_zero );
The Windows API is really dismissive of the concept of non-blocking single-threaded operation that most of the Unix world loves so much. There is no way to perform a non-blocking read or select() on any windows handle other than a socket. Your options are either to dive into the crazy mess of the overlapped (asynchronous) I/O API, or make one thread per handle, or mess around with WaitForMultipleObjects which can only listen to 64 things at once.
The one little workaround I found available for pipes is the PeekNamedPipe function, which can tell you if there is any data on the pipe. You can't wait with a timeout, but it at least gives the option of a crude check/sleep loop.
This is not a method, but a regular function. The first argument is a Win32 handle (not perl globref), which you can get from "FdGetOsFHandle" in Win32API::File.
The second and third are the buffer and number of bytes to read. If the first is not defined I enlarge it to the specified size, and if the size is undefined I use the existing size of the buffer. (if both are undef, I pass NULL which doesn't read anything).
The $bytes_read receives the value of the number of bytes written to the buffer, but you can ignore it because I resize the buffer to match. Set to undef if you like.
$bytes_read
The $bytes_available is the useful one, and receives the value of the number of bytes in the pipe.
$bytes_available
The final argument isn't useful for byte stream pipes, but I included it anyway.
Returns true if it succeeded. Check Win32::FormatMessage(Win32::GetLastError()) otherwise.
Win32::FormatMessage(Win32::GetLastError())
A well-maintained module for running child processes and bi-directional communication with them. However, the list of caveats for Win32 platform is somewhat alarming. (but I totally understand the difficulty it solves) I wasn't comfortable with using that in production, so I wrote this module with read/write on regular pipes.
A module to produce PowerShell command syntax using perl object methods.
Used by this module. Wraps a background process for Win32 environments where fork/exec is broken, such as Strawberry Perl.
I wish I'd found this module sooner, since it is something of an improvement over Win32::Process. However, it doesn't provide a method to check if a process is still running without killing the process, so ultimately not usable for this module.
Michael Conrad <mike@nrdvana.net>
This software is copyright (c) 2017 by Michael Conrad.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
To install Win32::PowerShell::IPC, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Win32::PowerShell::IPC
CPAN shell
perl -MCPAN -e shell install Win32::PowerShell::IPC
For more information on module installation, please visit the detailed CPAN module installation guide.