Pass information between independently running Powershell scrips - powershell

Sorry for being wage before. I'll try again:
The circumstances are too complicated to explain, but basically the problem is:
how to pass a string (max 20 chars) from one script, to another script running on the same machine.
the two scripts are running continuously in the background, on the same machine, under the same user context,
but can not be combined.
I can not dot-source one script in the other.
I have done it by having one script create a file with the string in it, in a directory monitored by the other script. So when it appears, the other script reads the information.
It works, but it feels "dirty". :)
I was wondering if there is a "best practice"-way to pass information between scripts or at least a more elegant way.
Thanks.

There are several ways for enabling two continuously running processes on the same host to communicate with each other, for instance named pipes:
# named pipe - server
$name = 'foo'
$namedPipe = New-Object IO.Pipes.NamedPipeServerStream($name, 'Out')
$namedPipe.WaitForConnection()
$script:writer = New-Object IO.StreamWriter($namedPipe)
$writer.AutoFlush = $true
$writer.WriteLine('something')
$writer.Dispose()
$namedPipe.Dispose()
# named pipe - client
$name = 'foo'
$namedPipe = New-Object IO.Pipes.NamedPipeClientStream('.', $name, 'In')
$namedPipe.Connect()
$script:reader = New-Object IO.StreamReader($namedPipe)
$reader.ReadLine()
$reader.Dispose()
$namedPipe.Dispose()
or TCP sockets:
# TCP socket - server
$addr = [ipaddress]'127.0.0.1'
$port = 1234
$endpoint = New-Object Net.IPEndPoint ($addr, $port)
$server = New-Object Net.Sockets.TcpListener $endpoint
$server.Start()
$cn = $server.AcceptTcpClient()
$stream = $cn.GetStream()
$writer = New-Object IO.StreamWriter($stream)
$writer.WriteLine('something')
$writer.Dispose()
$server.Stop()
# TCP socket - client
$server = '127.0.0.1'
$port = 1234
$client = New-Object Net.Sockets.TcpClient
$client.Connect($server, $port)
$stream = $client.GetStream()
$reader = New-Object IO.StreamReader($stream)
$reader.ReadLine()
$reader.Dispose()
$client.Dispose()

The easy solution would be to make the second script a function, then dot source it and just capture the return value into a variable.
I'm not entirely sure if this is possible otherwise, you could try $global:variableName or possibly run something as a job. If none of that works you could make the second script store the result in a file then access that file from the first script.

While your question is not very clear, i'll try to answer it.
It seems like you want to continue processing on your first script while your second script is doing (something).
You can put a specific operation in a Job (sort of like a thread) and receive it later with a while ($true) loop specifying conditions that meet your needs then break out of the loop after receiving the results from that Job, or thread.
Take a look at Get-Help | Start-Job for more info on that, or try to hit up google.
You can also import user-defined functions from another script by doing an import-module '.\pathtoscriptmodule.psm1' or just .\Pathtoscriptdefiningfunctions.ps1 to import functions you want to use from an outside script file.
Examples of using Start job..
$scriptblock = {
param($myParam);
# My commands here
}
Start-Job -ScriptBlock $scriptblock -args $myParamsIWantToPassToScriptblock
While ($true){
# To view status of that background job
Get-Job *
# Add your logic for fulfilling your conditons here, then
# break; <<uncomment to break out of loop
if (conditions met)
{
break;
}
}
# Gets the output from that job. -Keep keeps the output in memory
# if you want to call it multiple times
Receive-Job * -Keep

You could look into using runspaces.
http://learn-powershell.net/2013/04/19/sharing-variables-and-live-objects-between-powershell-runspaces/

Related

Make a Powershell respond to Register-ObjectEvent events in script mode

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.
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.

How do you pass values to another script under a foreach statement?

I want to run 1 script multiple times for each port select via a range and pass through the port details in which it needs to use to connect, I was trying to use the following:
$availableports = 7000..7050
while ($availableports -notcontains $SPort) {
[string]$SPort= Read-Host -Prompt 'S Ports'
}
while ($availableports -notcontains $FPort) {
[string]$FPort= Read-Host -Prompt 'F Ports'
}
$massport = ($SPort)..($FPort)
foreach ($Port in $massport) {
C:\PShell-Projects\Firmware\SCP-FMUPv2.ps1 -Port "$Port"
}
This works but will not move on to the next port until the referenced script has finished.
I would like to run them all in parallel.
I tried
$arg = #("-Port", $portm)
Start-Job -FilePath C:\PShell-Projects\Firmware\SCP-FMUPv2.ps1 -ArgumentList $arg
but the job becomes blocked and when I used Receive-Job it asks for the port to open a connection to, which should have been sent as part of the loop.
I seem to be missing some key information, but don't know where to start and well when looking up the information nothing seems to be standing out.
This works for me:
$jobs = #()
foreach ($Port in $massport) {
$jobs += start-job -FilePath "job.ps1" -ArgumentList #($port)
}
Receive-Job $jobs -Wait
(no named arguments in ArgumentList).
You can also take a look at Invoke-Parallel function, which simplifies running parallel tasks.
You could probably use -asjob after your C:\PShell-Projects\Firmware\SCP-FMUPv2.ps1 -Port "$Port command to have it just do them all simultaneously.
If the ps1 won't take -asjob, you might be able to wrap it with invoke-command {}

Displaying only changes when using get-content -wait

I created the following function which I wanted to use for a very simple CTI solution I have to use at work. This CTI process is writing all received calls to a text logile.
This function starts a new powershell Job and checks if the .log has been saved during the last 2 seconds and gets the last 4 lines of the log (receiving calls always creates 4 new lines).
During the job update I'm using regex to find the line with the phonenumber and time and append this to a richtextbox in a form.
In theory this works exactly as I want it to work. If I manually add new lines and save the file, it's always showing the timecode and phone number.
In the field however, this doesn't work as the CTI process is opening the file and doesn't save the it unless the process is shutting down.
I know that I can use get-content -wait to display new lines. I already tested this in the console and it's displaying new lines as soon as the .log is updated from the CTI process. What I don't know is how to rewrite the function to work with that, displaying only new lines and not all the old stuff when first running the script. I need to keep it in the job for a responsive form. Another thing is, that the computer running the form, doesn't have that much power. I don't know if get-content -wait could cause high memory usage after several hours. Maybe there are also some alternative solutions for a case like that available?
function start-CTIlogMonitoring
{
Param ($CTIlogPath)
Write-Debug "startCTI monitor"
Add-JobTracker -Name "CTILogger" -ArgumentList $CTIlogPath `
-JobScript {
#--------------------------------------------------
#TODO: Set a script block
#Important: Do not access form controls from this script block.
Param ($CTIlogPath) #Pass any arguments using the ArgumentList parameter
while ($true)
{
$diff = ((Get-ChildItem $CTIlogPath).LastWriteTime - (get-date)).totalseconds
Write-Debug "diff $diff"
if ($diff -gt -2)
{
Write-Debug "cti log DIFF detected"
Get-Content -Path "$CTIlogPath" -Tail 4
Start-Sleep -Seconds 1
}
}
#--------------------------------------------------
}`
-CompletedScript { Param ($Job) }`
-UpdateScript {
Param ($Job)
$results = Receive-Job -Job $Job | Out-String # Out-String required to get new lines in RTB
#get the stuff from results and make it more appearing to read for humans
if ($results -match '(Ein, E, (\d+))')
{
Write-debug "Incoming Call:"
$time = ([regex]'[0-9]{2}:[0-9]{2}:[0-9]{2}').Match($results)
$phoneNumber = ([regex]'Ein, E, (\d+)').Split($results)[1]
Write-Debug "$time ----> $phoneNumber"
if ($richtextboxCTIlogs.lines.count -eq 0)
{
$richtextboxCTIlogs.AppendText("$time ----> $phoneNumber")
}
else
{
$richtextboxCTIlogs.AppendText("`n$time ----> $phoneNumber")
}
$richtextboxCTIlogs.SelectionStart = $richtextboxCTIlogs.TextLength;
$richtextboxCTIlogs.ScrollToCaret()
}
<#else
{
Write-Debug "found nothin"
}#>
}
}

Save to array/list in parallel in powershell

I want to parallelize information gathering in my PS scripts.
My scripts usually do something along the lines of
foreach ($system in $systemlist) {
$system = Add-InformationToServerObj $system
}
and thus the $systemlist gets populated with more information which later gets used.
How can such a task which requires saving output to one shared list/array be parallelized?
Start-Job is an option, but it's quite high overhead in processing time since each job kicks of a new process and data will have to be serialized between the parent process and the job process.
Use Runspaces instead:
# Create initial sessionstate object for the runspaces
$InitialSessionState = [initialsessionstate]::Create()
# Import module that contains Add-InformationToServerObj
$InitialSessionState.ImportPSModule("InformationModule")
# Create and open the runspacepool
$RunspacePool = [runspacefactory]::CreateRunspacePool($InitialSessionState)
$RunspacePool.Open()
# Create a new PowerShell instance per "job", collect these along with the IAsyncResult handle (we'll need it later)
$Jobs = foreach($system in $systemlist)
{
$PSInstance = [powershell]::Create()
[void]$PSInstance.AddCommand('Add-InformationToServerObj').AddArgument($system)
New-Object psobject -Property #{
Instance = $PSInstance
IAResult = $PSInstance.BeginInvoke()
}
}
# Wait for runspaces to complete
while($InProgress = #($Jobs |Where-Object {-not $_.IAResult.IsCompleted})){
# Here you could also use Write-Progress
Write-Host "$($InProgress.Count) jobs still in progress..."
Start-Sleep -Milliseconds 500
}
# Collect the output
$systemlist = foreach($Job in $Jobs)
{
$Job.Instance.EndInvoke($Job.IAResult)
}
# Dispose of the runspacepool
$RunspacePool.Dispose()
The above is a very basic example and has zero error handling - consider using something like Invoke-Parallel or PoshRSJobs instead (PoshRSJobs can also be found on the gallery)
use this :
foreach ($system in $systemlist) {
start-job -scriptblock{$system = Add-InformationToServerObj $system }
}

Powershell command timeout

I am trying to execute a function or a scriptblock in powershell and set a timeout for the execution.
Basically I have the following (translated into pseudocode):
function query{
#query remote system for something
}
$computerList = Get-Content "C:\scripts\computers.txt"
foreach ($computer in $computerList){
$result = query
#do something with $result
}
The query can range from a WMI query using Get-WmiObject to a HTTP request and the script has to run in a mixed environment, which includes Windows and Unix machines which do not all have a HTTP interface.
Some of the queries will therefore necessarily hang or take a VERY long time to return.
In my quest for optimization I have written the following:
$blockofcode = {
#query remote system for something
}
foreach ($computer in $computerList){
$Job = Start-Job -ScriptBlock $blockofcode -ArgumentList $computer
Wait-Job $Job.ID -Timeout 10 | out-null
$result = Receive-Job $Job.ID
#do something with result
}
But unfortunately jobs seem to carry a LOT of overhead. In my tests a query that executes in 1.066 seconds (according to timers inside $blockofcode) took 6.964 seconds to return a result when executed as a Job. Of course it works, but I would really like to reduce that overhead. I could also start all jobs together and then wait for them to finish, but the jobs can still hang or take ridiculous amounts to time to complete.
So, on to the question: is there any way to execute a statement, function, scriptblock or even a script with a timeout that does not comprise the kind of overhead that comes with jobs? If possible I would like to run the commands in parallel, but that is not a deal-breaker.
Any help or hints would be greatly appreciated!
EDIT: running powershell V3 in a mixed windows/unix environment
Today, I ran across a similar question, and noticed that there wasn't an actual answer to this question. I created a simple PowerShell class, called TimedScript. This class provides the following functionality:
Method: Start() method to kick off the job, when you're ready
Method:GetResult() method, to retrieve the output of the script
Constructor: A constructor that takes two parameters:
ScriptBlock to execute
[int] timeout period, in milliseconds
It currently lacks:
Passing in arguments to the PowerShell ScriptBlock
Other useful features you think up
Class: TimedScript
class TimedScript {
[System.Timers.Timer] $Timer = [System.Timers.Timer]::new()
[powershell] $PowerShell
[runspace] $Runspace = [runspacefactory]::CreateRunspace()
[System.IAsyncResult] $IAsyncResult
TimedScript([ScriptBlock] $ScriptBlock, [int] $Timeout) {
$this.PowerShell = [powershell]::Create()
$this.PowerShell.AddScript($ScriptBlock)
$this.PowerShell.Runspace = $this.Runspace
$this.Timer.Interval = $Timeout
Register-ObjectEvent -InputObject $this.Timer -EventName Elapsed -MessageData $this -Action ({
$Job = $event.MessageData
$Job.PowerShell.Stop()
$Job.Runspace.Close()
$Job.Timer.Enabled = $False
})
}
### Method: Call this when you want to start the job.
[void] Start() {
$this.Runspace.Open()
$this.Timer.Start()
$this.IAsyncResult = $this.PowerShell.BeginInvoke()
}
### Method: Once the job has finished, call this to get the results
[object[]] GetResult() {
return $this.PowerShell.EndInvoke($this.IAsyncResult)
}
}
Example Usage of TimedScript Class
# EXAMPLE: The timeout period is set longer than the execution time of the script, so this will succeed
$Job1 = [TimedScript]::new({ Start-Sleep -Seconds 2 }, 4000)
# EXAMPLE: This script will fail. Even though Get-Process returns quickly, the Start-Sleep call will cause it to be terminated by its Timer.
$Job2 = [TimedScript]::new({ Get-Process -Name s*; Start-Sleep -Seconds 3 }, 2000)
# EXAMPLE: This job will fail, because the timeout is less than the script execution time.
$Job3 = [TimedScript]::new({ Start-Sleep -Seconds 3 }, 1000)
$Job1.Start()
$Job2.Start()
$Job3.Start()
Code is also hosted on GitHub Gist.
I think you might want to investigate using Powershell runspaces:
http://learn-powershell.net/2012/05/13/using-background-runspaces-instead-of-psjobs-for-better-performance/