Using a PowerShell script I will have to read and write to a console. We write an input and wait for an out that will be captured by $Reader.ReadLine(). But in some cases there wont be any output to get captured for the reader in that case the reader needs to look data from the stream and if there is no data the ReadLine() gets stuck/blocked waiting for the data from the console stream, whereas we need the ReadLine() to just wait for 5 seconds. If there is no data, it needs to get timed out and move on to the next command.
Please let me know whether there is any way to timeout $Reader.ReadLine() in PowerShell?
I see that in Java/C# we can use $Reader.ReadLine(1000) to timeout after 1 second but that doesn't seem to be working on PowerShell.
$tcpConnection = New-Object System.Net.Sockets.TcpClient($Computername, $Port)
$tcpStream = $tcpConnection.GetStream()
$reader = New-Object System.IO.StreamReader($tcpStream)
$writer = New-Object System.IO.StreamWriter($tcpStream)
$writer.AutoFlush = $true
$buffer = New-Object System.Byte[] 1024
$encoding = New-Object System.Text.AsciiEncoding
while ($tcpStream.DataAvailable) {
$reader.ReadLine()
}
if ($tcpConnection.Connected) {
$writer.WriteLine($username)
$reader.ReadLine()
$writer.WriteLine($password)
$reader.ReadLine()
try {
# if there is any data it will show here if there is no data then
# it should get timed out after 5 seconds
$Reader.ReadLine()
} catch {
Write-Host "Login Failed"
}
}
I would say that you should read this post C# Stream.Read with timeout
Converting that to your code sample, should end up with something like this.
$tcpConnection = New-Object System.Net.Sockets.TcpClient($Computername, $Port)
#This is one way you could try
$tcpConnection.ReceiveTimeout = 5000;
$tcpStream = $tcpConnection.GetStream()
$reader = New-Object System.IO.StreamReader($tcpStream)
$writer = New-Object System.IO.StreamWriter($tcpStream)
$writer.AutoFlush = $true
$buffer = New-Object System.Byte[] 1024
$encoding = New-Object System.Text.AsciiEncoding
while ($tcpStream.DataAvailable) {
$reader.ReadLine()
}
if ($tcpConnection.Connected) {
$writer.WriteLine($username)
$reader.ReadLine()
$writer.WriteLine($password)
$reader.ReadLine()
try {
# if there is any data it will show here if there is no data then
# it should get timed out after 5 seconds
$Reader.ReadLine()
} catch {
Write-Host "Login Failed"
}
}
Take it for a spin and let me know if it works or not.
Updated:
Updated to reflect the code to only contain the working solution.
Related
I would like to help understand how I may save the results of a variable.method output into another variable.
I would like to test creating a song but I am stuck on how to proceed.
Currently the voice outputs itself but doesn't save in the variable.
Is their a logical reason why? I've only studied powershell for a month.
Add-Type -AssemblyName System.Speech
$speak = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$man = $speak.Speak("Male Voice") # Outputs the Audio to host, but it does not store the result into Variable
$speak.SelectVoice("Microsoft Zira Desktop")
$woman = $speak.Speak("Female Voice")# Same as above
function song {
$man
$woman
song
}
The Speak method has no return value
public void Speak( string textToSpeak )
However it is possible to set the audio input to a file via SetOutputToWaveFile
You can then use SoundPlayer to play the audio back immediatly for better user experience.
Add-Type -AssemblyName System.Speech
$soundFilePath = "Test.wav"
# Save the Audio to a file
[System.Speech.Synthesis.SpeechSynthesizer] $voice = $null
Try {
$voice = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$voice.SetOutputToWaveFile($soundFilePath)
$voice.Speak("Male Voice")
$voice.SelectVoice("Microsoft Zira Desktop")
$voice.Speak("Female Voice")
} Finally {
# Make sure the wave file is closed
if ($voice) {
$voice.Dispose()
}
}
# Load the saved audio and play it back
[System.Media.SoundPlayer] $player = $null
Try {
$player = New-Object -TypeName System.Media.SoundPlayer($soundFilePath)
$player.PlaySync()
} Finally {
if ($player) {
$player.Dispose()
}
}
If you only need the audio in memory and you don't want to post-process anything you could write the data to a MemoryStream instead.
Add-Type -AssemblyName System.Speech
[System.IO.MemoryStream] $memoryStream = $null;
Try {
$memoryStream = New-Object -TypeName System.IO.MemoryStream
[System.Speech.Synthesis.SpeechSynthesizer] $voice = $null
Try {
$voice = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$voice.SetOutputToWaveStream($memoryStream)
$voice.Speak("Male Voice")
$voice.SelectVoice("Microsoft Zira Desktop")
$voice.Speak("Female Voice")
} Finally {
if ($voice) {
$voice.Dispose()
}
}
# Load the saved audio and play it back
[System.Media.SoundPlayer] $player = $null
Try {
$memoryStream.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null
$player = New-Object -TypeName System.Media.SoundPlayer($memoryStream)
$player.PlaySync()
} Finally {
if ($player) {
$player.Dispose()
}
}
} Finally {
if ($memoryStream) {
$memoryStream.Dispose()
}
}
You can't put the voice output into a variable, because it's not a value returned by the function but a "side-effect".
But there's no need to store the audio. It's much more effective to just store the text and generate the voice output each time the script is run.
Edit: I used .SpeakAsync and sleep to have better control of the timing. Adjust the delay number so it fits the flow of each line.
Add-Type -AssemblyName System.Speech
$male = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$male.SelectVoice("Microsoft David Desktop")
$female = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$female.SelectVoice("Microsoft Zira Desktop")
function male([string] $text, [int] $duration) {
$male.SpeakAsync($text) | Out-Null
sleep -Milliseconds $duration
}
function female([string] $text, [int] $duration) {
$female.SpeakAsync($text) | Out-Null
sleep -Milliseconds $duration
}
function song {
male 'hello' 500
female 'goodbye' 500
}
song
I have a powershell that is downloading some XML data over TCP. I need to have all the data written to a file. Each line of the response needs to be written to a new line in the file. The code below only gets the first line. Ignore the extra readline's because the first few lines are garbage data I don't need. How can I continue to write each line of the response until there are none left? Couldn't find anything else on this.
$server = "192.168.1.173"
$port = "45678"
$password = "password"
while (1) {
$tcpConnection = New-Object System.Net.Sockets.TcpClient($server, $port)
$tcpStream = $tcpConnection.GetStream()
$reader = New-Object System.IO.StreamReader($tcpStream)
$writer = New-Object System.IO.StreamWriter($tcpStream)
$writer.AutoFlush = $true
if ($tcpConnection.Connected) {
$writer.Write("<StageDisplayLogin>");
$writer.Write($password);
$writer.WriteLine("</StageDisplayLogin>");
$ProPresenterData = $reader.ReadLine()
$ProPresenterData = $reader.ReadLine()
$ProPresenterData = $reader.ReadLine()
$ProPresenterData | Out-File -Encoding "UTF8" ProPresenter.xml
Start-Sleep -m 200
while ($tcpStream.DataAvailable) {
$ProPresenterData = $reader.ReadLine()
Add-content -Path ProPresenter.xml -Value "$ProPresenterData"
}
}
$reader.Close()
$writer.Close()
$tcpConnection.Close()
"Wrote file ProPresenter.xml"
$ProPresenterData
Start-Sleep -m 500
}
I can only partially answer your problem. The reason why you only get one line is because of your while loop. There is no trigger for it to go back to the beginning and so it does your loop just one time.
With a foreach you can loop through all the lines in your file:
$file = get-content your.xml
foreach($line in $file){
$line # this would print each line , one after the other.
}
Alternativly you can use a for-loop if you can determine the length of the file in advance.
Having said that, is it an option for you to first fully download the file and then write it into a different file? Or, is it a file that is being constantly renewed with lines added on a certain interval?
I would like to ask you, how it is possible to handle multiple connection threads.
I have implemented TCP server in the following way:
$endpoint = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::Any, 8989)
$listener = New-Object System.Net.Sockets.TcpListener $endpoint
$listener.Start()
do {
$client = $listener.AcceptTcpClient() # will block here until connection
$stream = $client.GetStream();
$reader = New-Object System.IO.StreamReader $stream
do {
$line = $reader.ReadLine()
Write-Host $line -fore cyan
} while ($line -and $line -ne ([char]4))
$reader.Dispose()
$stream.Dispose()
$client.Dispose()
} while ($line -ne ([char]4))
$listener.Stop()
This code can handle just one thread in time.
Can you give me an advice on how to create a TCP server in PowerShell that can handle multiple clients?
For handling multiple clients you need multiple threads and for that you need to use runspaces. Below is the working code which accepts multiple clients and do the processing of each client in separate thread (runspace)
$Global:Listener = [HashTable]::Synchronized(#{})
$Global:CnQueue = [System.Collections.Queue]::Synchronized((New-Object System.collections.queue))
$Global:space = [RunSpaceFactory]::CreateRunspace()
$space.Open()
$space.SessionStateProxy.setVariable("CnQueue", $CnQueue)
$space.SessionStateProxy.setVariable("Listener", $Listener)
$Global:newPowerShell = [PowerShell]::Create()
$newPowerShell.Runspace = $space
$Timer = New-Object Timers.Timer
$Timer.Enabled = $true
$Timer.Interval = 1000
Register-ObjectEvent -SourceIdentifier MonitorClientConnection -InputObject $Timer -EventName Elapsed -Action {
While($CnQueue.count -ne 0) {
$client = $CnQueue.Dequeue()
$newRunspace = [RunSpaceFactory]::CreateRunspace()
$newRunspace.Open()
$newRunspace.SessionStateProxy.setVariable("client", $client)
$newPowerShell = [PowerShell]::Create()
$newPowerShell.Runspace = $newRunspace
$process = {
$stream = $client.GetStream();
$reader = New-Object System.IO.StreamReader $stream
[console]::WriteLine("Inside Processing")
# You have client here so do whatever you want to do here.
# This is a separate thread so if you write blocking code here, it will not impact any other part of the program
}
$jobHandle = $newPowerShell.AddScript($process).BeginInvoke()
#jobHandle you need to save for future to cleanup
}
}
$listener = {
$Listener['listener'] = New-Object System.Net.Sockets.TcpListener("127.0.0.1", "1234")
$Listener['listener'].Start()
[console]::WriteLine("Listening on :1234")
while ($true) {
$c = $Listener['listener'].AcceptTcpClient()
If($c -ne $Null) {
[console]::WriteLine("{0} >> Accepted Client " -f (Get - Date).ToString())
$CnQueue.Enqueue($c)
}
Else {
[console]::WriteLine("Shutting down")
Break
}
}
}
$Timer.Start()
$Global:handle = $newPowerShell.AddScript($listener).BeginInvoke()
For more detailed example please go here
I use this powershell script for getting output stream of telnet request.
Function Get-Telnet {
Param (
[Parameter(ValueFromPipeline=$true)]
[string[]]$Commands = #(),
[string]$RemoteHost = "0.0.0.0",
[string]$Port = "23",
[int]$WaitTime = 1000,
[string]$OutputPath = "C:\temp\telnet_output.log"
)
#Attach to the remote device, setup streaming requirements
$Socket = New-Object System.Net.Sockets.TcpClient($RemoteHost, $Port)
if ($Socket) {
$Stream = $Socket.GetStream()
$Writer = New-Object System.IO.StreamWriter($Stream)
$Buffer = New-Object System.Byte[] 1024
$Encoding = New-Object System.Text.AsciiEncoding
#Now start issuing the commands
foreach ($Command in $Commands) {
$Writer.WriteLine($Command)
$Writer.Flush()
Start-Sleep -Milliseconds $WaitTime
}
#All commands issued, but since the last command is usually going to be
#the longest let's wait a little longer for it to finish
Start-Sleep -Milliseconds ($WaitTime * 4)
$Result = ""
#Save all the results
while ($Stream.DataAvailable) {
$Read = $Stream.Read($Buffer, 0, 1024)
$Result += ($Encoding.GetString($Buffer, 0, $Read))
}
} else {
$Result = "Unable to connect to host: $($RemoteHost):$Port"
}
#Done, now save the results to a file
#$Result | Out-File $OutputPath
$Result
}
This works for a Cisco switch and a telnet server, but for the Alcatel OmniPCX and the HPUX the stream reading is empty.
I'm having trouble writing a PowerShell script that can both send and receive data via a TCP connection. It only seems to let me do one or the other.
Below is what I have so far. I want to listen and wait for a connection, then once established, receive a string containing an IP address, do some fancy lookup to see what user is logged into that machine, then send back the username. If I only send data, it works. If I only receive data, it works. If I try to do both, only the receive works. What am I doing wrong?
$port = 1234
do {
$user = ""
$endpoint = new-object System.Net.IPEndPoint ([system.net.ipaddress]::any, $port)
$listener = new-object System.Net.Sockets.TcpListener $endpoint
$listener.start()
$client = $listener.AcceptTcpClient() # will block here until connection
$stream = $client.GetStream();
$reader = New-Object System.IO.StreamReader $stream
$writer = New-Object System.IO.StreamWriter $stream
$add = $reader.ReadLine()
#$reader.close()
write-host "Request from " $add
if($add) {
$user = & wmic /Node:$add ComputerSystem Get UserName
write-host "User returned is " $user[2]
}
if($user[2] -eq "ERROR:") {
$user[2] = "ErrNoUserW"
} elseif(!$user[2]) {
$user[2] = "ErrServerW"
}
$writer.Write($user[2])
#$writer.close()
$stream.close()
$client.close()
$listener.stop()
} while(1)
I thought that the stream could only be read or write at any given time. Have you tried closing the stream then re-opening it?