How to suppress "keyboard-interactive" prompts in plink.exe - powershell

I have a PowerShell script that calls plink.exe regularly. Normally, the two output lines about keyboard-interactive prompts are simply annoying.
However, when run using Start-Job, they get output as error text as soon as I call Receive-Job.
Is there any way to suppress these? I'd rather not suppress all errors.
My test code:
$test_scriptblock = [scriptblock]::Create(#"
param(
`$argumentlist
)
`$pw = `$argumentlist.pw
& 'C:\Program Files\Putty\Plink.exe' -ssh `"admin#*.*.*.*" -pw `$pw -batch whoami
"#)
$testParm = #{
pw = Read-Host "password"
}
$testjob = Start-Job -scriptblock $test_scriptblock -Argumentlist $testParm
$i = 0
do {
$i++
sleep 2
$results = Receive-Job $testjob
ForEach ($result in $results) {
Write-Host $result
}
if ($testjob.State -eq "Completed") {
$jobcompleted = $true
}
If ($i -gt 10) {
Stop-job $testjob
$jobcompleted = $true
}
} until ($jobcompleted)

Just add the stderr redirect to your plink or pscp commandline, to an extra dummy file, like
pscp ... 2> stderr.txt
With a caveat that it may swallow other valid error msgs, at your own risk :)

There's no way to suppress keyboard-interactive prompts in Plink.
I suggest you use a native .NET SSH implementation, like SSH.NET instead of driving an external console application.

Ir was a bit cumbersome, but finally I managed to suppress the "keyboard-interactive" messages this way:
[String] $Plink = 'C:\Program Files\PuTTY\plink.exe'
[Array] $PlinkPar = #("-ssh", "-l", $usr, "-pw", $pwd, $hst) # Set plink parameters
[Boolean] $PlinkOK = $True
Write-Host "Accept possibly unknown host key"
"y" | & $Plink $PlinkPar "exit" 2>&1 | Tee-Object -Variable PlinkOut | Out-Null
$PlinkOut | Foreach-Object {
$PlinkStr = $_.ToString()
If ($_ -is [System.Management.Automation.ErrorRecord]) {
If (! $PlinkStr.Contains("eyboard-interactive")) {
Write-Host "Error: $PlinkStr"
$PlinkOK = $False
}
} else {
Write-Host "$PlinkStr"
}
}
If (! $PlinkOK) { exit }
$PlinkPar += "-batch
And the output is like this:
>powershell .\InstCR.ps1 -usr myuser -pwd mypassword -hst doesnotexist
Accept possibly unknown host key
Error: Unable to open connection:
Error: Host does not exist
This plink call is just to accept a possibly unknown host key (without "-batch" and piping the "y" to answer the prompt). Then "-batch" is added to the Plink parameters to be used on all subsequent plink calls.

Related

Send confirmation message if command ran with Invoke-Expression completes successfully

I'm writing a PowerShell script to add / remove people from a distribution group. I want to send a message if the action was successful and another if it failed. This is part of the script:
foreach ($x in Get-Content $pathfile\inputfile.txt) {
$USER = $x.Split(',')[0]
$ACTION = $x.Split(',')[1]
$COMMAND = (Write-Output "$ACTION-DistributionGroupMember -Identity 'Group Name' -Member $USER")
if ($ACTION -ieq "remove") {
$COMMAND = $COMMAND + ' -Confirm:$false'
Invoke-Expression $COMMAND
}
else {
Invoke-Expression $COMMAND
}
}
inputfile.txt, for the sake of information is:
jdoe#example.com,Add
jsmith#example.com,Remove
I've tried using $? and $lasExitCode but neither of those worked as expected as they only consider the output of "Invoke-Expression" and that is always successful.
What I am expecting is:
foreach ($x in Get-Content $pathfile\inputfile.txt) {
$USER = $x.Split(',')[0]
$ACTION = $x.Split(',')[1]
$COMMAND = (Write-Output "$ACTION-DistributionGroupMember -Identity 'Group Name' -Member $USER")
if ($ACTION -ieq "remove") {
$COMMAND = $COMMAND + ' -Confirm:$false'
Invoke-Expression $COMMAND
#if $COMMAND successful: Write-Output "$ACTION on $USER succeeded."
#if $COMMAND unsuccessful: Write-Output "$ACTION on $USER failed."
}
else {
Invoke-Expression $COMMAND
#if $COMMAND successful: Write-Output "$ACTION on $USER succeeded."
#if $COMMAND unsuccessful: Write-Output "$ACTION on $USER failed."
}
}
$? won't work because even if the command fails, Invoke-Expression was invoked successfully.
Use the & call operator to invoke the call directly instead, and $? will work. For the conditional parameter argument, use splatting!
foreach ($x in Get-Content $pathfile\inputfile.txt) {
$user,$action,$null = $x.Split(',')
# construct command name
$command = "$ACTION-DistributionGroupMember"
# organize the parameter arguments in a hashtable for splatting later
$paramArgs = #{
Identity = 'Group Name'
Member = $USER
}
# conditionally add the `-Confirm` parameter
if ($ACTION -ieq "remove") {
$paramArgs['Confirm'] = $false
}
# invoke the command with the call operator
& $command #paramArgs
if($?){
# command invocation suceeded
}
else {
# command invocation failed
}
}

Calling cmd.exe using PowerShell, spawn multiple instances asynchronously. Don't wait for cmd.exe to close

I am writing a function that will establish remote Wireshark packet capture sessions for VMs living on ESXi hosts.
This function will be given an array of vNIC objects, then based on information for each vNIC, will use plink and Wireshark locally to establish a remote live pcap feed using plink stdout > wireshark stdin.
I have decided to use cmd.exe to call plink/wireshark as the nuances of pipes in calling external commands in powershell have defeated me, happy to be shown another way to do this though.
I need to call the plink/wireshark command like this:
"C:\Program Files\PuTTY\plink.exe" -batch -l root -pw PASSWORD -P 22 remotehost.com pktcap-uw --switchport 1112222 -o - |
"C:\Program Files\Wireshark\wireshark.exe" -o "gui.window_title:VMName - Network adapter X - 1112222 - Portgroupname - VMHost" -k -i -
The code to produce the above command looks like this:
$command = "`"$($PlinkPath)`" -batch -l $($ESXiRootCredential.Username) -pw $($ESXiRootCredential.GetNetworkCredential().password) -P 22 $($currentHost.Name) pktcap-uw --switchport $($currentvNICSwitchPortInfo.portID) -o - `| `"$($WireSharkPath)`" -o `"gui.window_title:$($currentvNICSwitchPortInfo.VM.Name) - $($currentvNICSwitchPortInfo.Name) - $($currentvNICSwitchPortInfo.PortID) - $($currentvNICSwitchPortInfo.PortgroupName) - $($currentvNICSwitchPortInfo.VMHost.Name)`" -k -i - &"
This portion of the script works. The issue is with calling $command in cmd.exe using powershell but asynchronously so that if I pass in multiple vNICs, the for loop will spawn cmd.exe with $command then immediately move onto doing the same for the next vNIC, and so on.
I've tried a combination of several methods:
Invoke-Command
Invoke-Expression
& cmd.exe /c
etc.
#Requires -Modules VMware.VimAutomation.Common
function New-RemoteVMvNICPacketCapture {
[CmdletBinding()]
Param(
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[VMware.VimAutomation.Types.NetworkAdapter[]]$vNIC,
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory=$true )]
[System.Management.Automation.PSCredential]$ESXiRootCredential,
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory=$false)]
[String]$WireSharkPath = 'C:\Program Files\Wireshark\wireshark.exe',
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory=$false)]
[String]$PlinkPath = 'C:\Program Files\PuTTY\plink.exe'
)
Begin {
$ErrorActionPreference = 'Stop'
Write-Debug $MyInvocation.MyCommand.Name
# Import function needed to get switch port ID(s)
try {
. "$PSScriptRoot\Get-VMvNICSwitchPortInfo.ps1"
} catch {
Write-Error "Unable to import function Get-VMvNICSwitchPortInfo, exiting!"
break
}
}
Process {
try {
# Get unique list of ESXi hosts from supplied vNICs(s)
$uniqueESXiHosts = $vNIC |
Sort-Object -Property {$_.Parent.VMHost} |
Select-Object #{N="VMHost";E={$_.Parent.VMHost}} -Unique
foreach ($ESXiHost in $uniqueESXiHosts) {
# Get VMHost handle from current array index
$currentHost = $ESXiHost.VMHost
$sshService = $currentHost |
Get-VMHostService |
Where-Object {$_.Key -eq 'TSM-SSH'}
if ($sshService.Running -ne $true) {
$sshService | Start-VMHostService
}
if (-not (Test-NetConnection -Port 22 -ComputerName $currentHost)) {
Write-Error "Unable to connect to \"$currentHost\" on port 22. Skipping the following VMs: " + + ([String]::Join(', ',($vNIC | Where-Object {$_.Parent.VMHost -eq $currentHost} | Sort-Object -Property Parent).Parent.Name))
break
} else {
Write-Host "Able to connect to $currentHost on port 22"
}
foreach ($vnic in ($vNIC | Where-Object {$_.Parent.VMHost -eq $currentHost} | Sort-Object -Property Parent)) {
$currentvNICSwitchPortInfo = $vnic | Get-VMvNICSwitchPortInfo
# Create remote wireshark capture session
$command = "`"$($PlinkPath)`" -batch -l $($ESXiRootCredential.Username) -pw $($ESXiRootCredential.GetNetworkCredential().password) -P 22 $($currentHost.Name) pktcap-uw --switchport $($currentvNICSwitchPortInfo.portID) -o - `| `"$($WireSharkPath)`" -o `"gui.window_title:$($currentvNICSwitchPortInfo.VM.Name) - $($currentvNICSwitchPortInfo.Name) - $($currentvNICSwitchPortInfo.PortID) - $($currentvNICSwitchPortInfo.PortgroupName) - $($currentvNICSwitchPortInfo.VMHost.Name)`" -k -i - &"
Write-Host $command -ForegroundColor Yellow
Invoke-Command -ScriptBlock {& cmd.exe /c "$($command)"}
}
if ($sshService.Running -eq $false) {
$sshService | Stop-VMHostService -Confirm:$false
}
}
} catch [Exception] {
Write-Host $_.Exception.Message
throw "Unable to establish remote pcap"
}
}
End {}
}
This should spawn multiple instances of plink/wireshark one after another without having to wait. At the moment it spawns the first one and then waits for wireshark to close (and the associated cmd.exe process) before continuing.

PowerShell & cURL - Get JSON Response and Check HTTP Response Code [duplicate]

Is it possible to redirect stdout from an external program to a variable and stderr from external programs to another variable in one run?
For example:
$global:ERRORS = #();
$global:PROGERR = #();
function test() {
# Can we redirect errors to $PROGERR here, leaving stdout for $OUTPUT?
$OUTPUT = (& myprogram.exe 'argv[0]', 'argv[1]');
if ( $OUTPUT | select-string -Pattern "foo" ) {
# do stuff
} else {
$global:ERRORS += "test(): oh noes! 'foo' missing!";
}
}
test;
if ( #($global:ERRORS).length -gt 0 ) {
Write-Host "Script specific error occurred";
foreach ( $err in $global:ERRORS ) {
$host.ui.WriteErrorLine("err: $err");
}
} else {
Write-Host "Script ran fine!";
}
if ( #($global:PROGERR).length -gt 0 ) {
# do stuff
} else {
Write-Host "External program ran fine!";
}
A dull example however I am wondering if that is possible?
One option is to combine the output of stdout and stderr into a single stream, then filter.
Data from stdout will be strings, while stderr produces System.Management.Automation.ErrorRecord objects.
$allOutput = & myprogram.exe 2>&1
$stderr = $allOutput | ?{ $_ -is [System.Management.Automation.ErrorRecord] }
$stdout = $allOutput | ?{ $_ -isnot [System.Management.Automation.ErrorRecord] }
The easiest way to do this is to use a file for the stderr output, e.g.:
$output = & myprogram.exe 'argv[0]', 'argv[1]' 2>stderr.txt
$err = get-content stderr.txt
if ($LastExitCode -ne 0) { ... handle error ... }
I would also use $LastExitCode to check for errors from native console EXE files.
You should be using Start-Process with -RedirectStandardError -RedirectStandardOutput options. This other post has a great example of how to do this (sampled from that post below):
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode
This is also an alternative that I have used to redirect stdout and stderr of a command line while still showing the output during PowerShell execution:
$command = "myexecutable.exe my command line params"
Invoke-Expression $command -OutVariable output -ErrorVariable errors
Write-Host "STDOUT"
Write-Host $output
Write-Host "STDERR"
Write-Host $errors
It is just another possibility to supplement what was already given.
Keep in mind this may not always work depending upon how the script is invoked. I have had problems with -OutVariable and -ErrorVariable when invoked from a standard command line rather than a PowerShell command line like this:
PowerShell -File ".\FileName.ps1"
An alternative that seems to work under most circumstances is this:
$stdOutAndError = Invoke-Expression "$command 2>&1"
Unfortunately, you will lose output to the command line during execution of the script and would have to Write-Host $stdOutAndError after the command returns to make it "a part of the record" (like a part of a Jenkins batch file run). And unfortunately it doesn't separate stdout and stderr.
In case you want to get any from a PowerShell script and to pass a function name followed by any arguments you can use dot sourcing to call the function name and its parameters.
Then using part of James answer to get the $output or the $errors.
The .ps1 file is called W:\Path With Spaces\Get-Something.ps1 with a function inside named Get-It and a parameter FilePath.
Both the paths are wrapped in quotes to prevent spaces in the paths breaking the command.
$command = '. "C:\Path Spaces\Get-Something.ps1"; Get-It -FilePath "W:\Apps\settings.json"'
Invoke-Expression $command -OutVariable output -ErrorVariable errors | Out-Null
# This will get its output.
$output
# This will output the errors.
$errors
Copied from my answer on how to capture both output and verbose information in different variables.
Using Where-Object(The alias is symbol ?) is an obvious method, but it's a bit too cumbersome. It needs a lot of code.
In this way, it will not only take longer time, but also increase the probability of error.
In fact, there is a more concise method that separate different streams to different variable in PowerShell(it came to me by accident).
# First, declare a method that outputs both streams at the same time.
function thisFunc {
[cmdletbinding()]
param()
Write-Output 'Output'
Write-Verbose 'Verbose'
}
# The separation is done in a single statement.Our goal has been achieved.
$VerboseStream = (thisFunc -Verbose | Tee-Object -Variable 'String' | Out-Null) 4>&1
Then we verify the contents of these two variables
$VerboseStream.getType().FullName
$String.getType().FullName
The following information should appear on the console:
PS> System.Management.Automation.VerboseRecord
System.String
'4>&1' means to redirect the verboseStream to the success stream, which can then be saved to a variable, of course you can change this number to any number between 2 and 5.
Separately, preserving formatting
cls
function GetAnsVal {
param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Object[]][AllowEmptyString()]$Output,
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$firstEncNew="UTF-8",
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$secondEncNew="CP866"
)
function ConvertTo-Encoding ([string]$From, [string]$To){#"UTF-8" "CP866" "ASCII" "windows-1251"
Begin{
$encFrom = [System.Text.Encoding]::GetEncoding($from)
$encTo = [System.Text.Encoding]::GetEncoding($to)
}
Process{
$Text=($_).ToString()
$bytes = $encTo.GetBytes($Text)
$bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
$encTo.GetString($bytes)
}
}
$all = New-Object System.Collections.Generic.List[System.Object];
$exception = New-Object System.Collections.Generic.List[System.Object];
$stderr = New-Object System.Collections.Generic.List[System.Object];
$stdout = New-Object System.Collections.Generic.List[System.Object]
$i = 0;$Output | % {
if ($_ -ne $null){
if ($_.GetType().FullName -ne 'System.Management.Automation.ErrorRecord'){
if ($_.Exception.message -ne $null){$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$exception.Add($Temp)}
elseif ($_ -ne $null){$Temp=$_ | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$stdout.Add($Temp)}
} else {
#if (MyNonTerminatingError.Exception is AccessDeniedException)
$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;
$all.Add($Temp);$stderr.Add($Temp)
}
}
$i++
}
[hashtable]$return = #{}
$return.Meta0=$all;$return.Meta1=$exception;$return.Meta2=$stderr;$return.Meta3=$stdout;
return $return
}
Add-Type -AssemblyName System.Windows.Forms;
& C:\Windows\System32\curl.exe 'api.ipify.org/?format=plain' 2>&1 | set-variable Output;
$r = & GetAnsVal $Output
$Meta2=""
foreach ($el in $r.Meta2){
$Meta2+=$el
}
$Meta2=($Meta2 -split "[`r`n]") -join "`n"
$Meta2=($Meta2 -split "[`n]{2,}") -join "`n"
[Console]::Write("stderr:`n");
[Console]::Write($Meta2);
[Console]::Write("`n");
$Meta3=""
foreach ($el in $r.Meta3){
$Meta3+=$el
}
$Meta3=($Meta3 -split "[`r`n]") -join "`n"
$Meta3=($Meta3 -split "[`n]{2,}") -join "`n"
[Console]::Write("stdout:`n");
[Console]::Write($Meta3);
[Console]::Write("`n");

Output ssh command to text file in powershell

I am trying to output the following command to a text file in powershell, but I cannot seem to get it working:
ssh -v git#git.assembla.com | Out-File C:\output.txt
As stated in the post below with using native apps, you could try using Start-Process, e.g.
Start-Process ssh "-v git#git.assembla.com" -NoNewWindow -RedirectStandardOutput stdOut.log -RedirectStandardError stdErr.log; gc *.log; rm *.log
Working on the same problem I made a detail post on my blog How to SSH from Powershell Using Putty\Plink but the short version is this bit of code. But sure you try it after installing putty.
Function Invoke-SSHCommands {
Param($Hostname,$Username,$Password, $CommandArray, $PlinkAndPath, $ConnectOnceToAcceptHostKey = $true)
$Target = $Username + '#' + $Hostname
$plinkoptions = "-ssh $Target -pw $Password"
#Build ssh Commands
$remoteCommand = ""
$CommandArray | % {$remoteCommand += [string]::Format('{0}; ', $_) }
#plist prompts to accept client host key. This section will login and accept the host key then logout.
if($ConnectOnceToAcceptHostKey)
{
$PlinkCommand = [string]::Format('echo y | & "{0}" {1} exit', $PlinkAndPath, $plinkoptions )
#Write-Host $PlinkCommand
$msg = Invoke-Expression $PlinkCommand
}
#format plist command
$PlinkCommand = [string]::Format('& "{0}" {1} "{2}"', $PlinkAndPath, $plinkoptions , $remoteCommand)
#ready to run the following command
#Write-Host $PlinkCommand
$msg = Invoke-Expression $PlinkCommand
$msg
}
$PlinkAndPath = "C:\Program Files (x86)\PuTTY\plink.exe"
$Username = "remoteshell"
$Password = "pa$$w0rd"
$Hostname = "Linuxhost"
$Commands = #()
$Commands += "ls"
$Commands += "whoami"
Invoke-SSHCommands -User $Username -Hostname $Hostname -Password $Password -PlinkAndPath $PlinkAndPath -CommandArray $Commands

Automating Telnet with PowerShell

How can I write a PowerShell script to automate this set of commands?
Telnet to a machine,
execute few commands,
analyze at the output in the telnet window,
based on that output, send few more commands
Ok this isn't the most elegant solution, and it does rely on shudder VBscript but here it goes...
Create a VBScript to actually expedite the telnet session, this is an example
set oShell = CreateObject("WScript.Shell")
oShell.run("Telnet")
WScript.Sleep 1000
oShell.SendKeys("Open 127.0.0.1 23")
WScript.Sleep 1000
oShell.SendKeys("{Enter}")
WScript.Sleep 1000
oShell.SendKeys("n")
WScript.Sleep 1000
oShell.SendKeys("{Enter}")
WScript.Sleep 1000
oShell.SendKeys"MyName"
WScript.Sleep 1000
oShell.SendKeys("{Enter}")
WScript.Sleep 1000
oShell.SendKeys("MyPassword")
WScript.Sleep 1000
oShell.SendKeys("{Enter}")
WScript.Sleep 1000
oShell.SendKeys("MyCommand")
WScript.Sleep 1000
oShell.SendKeys("{Enter}")
WScript.Sleep 1000
Then use Powershell to invoke that script and pass it the commands you want executing, in the example below these commands are stored in a file called CommandList.txt
function Connect-MyTelnet{
Param(
[string] $IPAddress,
[string] $Port,
[string] $UserName,
[string] $Password,
[string] $cmdlistPath
)
## - Setting default values:
if($port -eq $null){ $Port = "23"; };
if($cmdlistPath -eq $null) { $CmdlistPath = 'c:\temp\cmdlist.txt'; };
## create vbscript file: MyTelnetSession.vbs
## - For Microsoft Telnet:
$MyVBScript = #"
set oShell = CreateObject("WScript.Shell")`r`n
oShell.run("Telnet")`r`n
WScript.Sleep 1000`r`n
oShell.SendKeys("Open $IPAddress $Port")`r`n
WScript.Sleep 1000`r`n
oShell.SendKeys("{Enter}")`r`n
WScript.Sleep 1000`r`n
oShell.SendKeys("n")`r`n
WScript.Sleep 1000`r`n
oShell.SendKeys("{Enter}")`r`n
WScript.Sleep 1000`r`n
oShell.SendKeys("$UserName")`r`n
WScript.Sleep 1000`r`n
oShell.SendKeys("{Enter}")`r`n
WScript.Sleep 1000`r`n
oShell.SendKeys("$Password")`r`n
WScript.Sleep 1000`r`n
oShell.SendKeys("{Enter}")`r`n
WScript.Sleep 1000`r`n
"#;
## - Get file with telnet commands:
[array] $Cmdlist = Get-Content $cmdlistPath;
## loop through and build each telnet command line:
foreach($cmd in $cmdlist)
{
## - Build VBscript lines:
$MyVBScript += 'oShell.SendKeys("'+$cmd+'")'+"`r`n";
$MyVBScript += "WScript.Sleep 1000`r`n";
$MyVBScript += 'oShell.SendKeys("{Enter}")'+"`r`n";
$MyVBScript += 'WScript.Sleep 1000'+"`r`n";
}
## - Close Telnet Session:
$MyVBScript += 'oShell.SendKeys(" QUIT")'+"`r`n";
$MyVBScript += "WScript.Sleep 1000`r`n";
$MyVBScript += 'oShell.SendKeys("{Enter}")'+"`r`n";
$MyVBScript += 'WScript.Sleep 1000'+"`r`n";
## - Save and execute generated VBscript:
$MYVBScript | Out-File -FilePath c:\temp\MyTelnet.vbs -Encoding ASCII;
& c:\temp\MyTelnet.vbs
}; Set-Alias ct Connect-MyTelnet;
And that should do what you are asking...
Note: Not my solution, found from this blog post and I have made use of it once or twice.
Rather than try to automate a telnet executable, just create the socket and issue the commands, read them back, and make decisions based on that. Here is an oversimplified example connecting to my local web server:
function test() {
$msg = [System.Text.Encoding]::ASCII.GetBytes("GET / HTTP/1.0`r`nHost: localhost`r`n`r`n")
$c = New-Object System.Net.Sockets.TcpClient("localhost", 80)
$str = $c.GetStream()
$str.Write($msg, 0, $msg.Length)
$buf = New-Object System.Byte[] 4096
$count = $str.Read($buf, 0, 4096)
[System.Text.Encoding]::ASCII.GetString($buf, 0, $count)
$str.Close()
$c.Close()
}
Obviously you would need to change it from port 80, and pass a username/password instead of a web request header... but this should be enough to get you started.
I wouldn't do anything with sockets here because you are going to need to implement at least parts of the telnet spec. If I remember, that spec is a bit funny. But there are some .NET telnet implementations listed here: C# Telnet Library that you can probably adapt or use directly from powershell in the same way that Goyuix is using the socket code in his answer.
I suggest you to use TeraTerm this free software.
you can telnet to your machine, and then run a TTL script.
it is very powerful and reliable. I am using it every day for my work.
you can do more search if you are interested.
example of TTL script:
i = 100
do while i>0
sendln 'un 1357'
wait '>'
sendln '.w 4 42800024 0000000a'
wait '>'
sendln '.w 4 42800014 00000004'
wait 'in service'
sendln 'info'
wait'>'
sendln 'start'
wait '20'#13#10'>' '0'#13#10'>'
if result!=2 then
break
endif
i = i - 1
loop
Here's a very basic Telnet client in PowerShell. It's essentially just the .net Framework's TcpClient, with some extra code to reject any IAC commands (i.e. when negotiating its abilities with the server it just says "I don't/won't do that" to all requests, ensuring that the most basic NVT implementation can be used).
Code maintained here: https://gist.github.com/JohnLBevan/e28fbb6c0dfdd45a21e03c104999c212
Function New-TelnetClient {
[CmdletBinding()]
Param (
[Parameter()]
[string]$ComputerName = '127.0.0.1'
,
[Parameter()]
[int]$PortNo = 23
,
[Parameter()]
[System.Text.Encoding]$Encoding = [System.Text.Encoding]::ASCII
,
[Parameter()]
[int]$BufferSize = 1024
)
[System.Net.Sockets.TcpClient]$telnet = New-Object 'System.Net.Sockets.TcpClient'
try {
$telnet.PSTypeNames.Add('ClearChannel.Net.Sockets.TelnetClient')
$telnet | Add-Member -MemberType 'NoteProperty' -Name 'Encoding' -Value ($Encoding)
$telnet | Add-Member -MemberType 'NoteProperty' -Name 'EndOfCommand' -Value ([System.Environment]::NewLine)
$telnet | Add-Member -MemberType 'NoteProperty' -Name 'BufferSize' -Value ($BufferSize)
$telnet.Connect($ComputerName, $PortNo)
$telnet | Add-Member -MemberType 'NoteProperty' -Name 'Writer' -Value (New-Object -TypeName 'System.IO.StreamWriter' -ArgumentList ($telnet.GetStream()))
$telnet.Writer.AutoFlush = $true
$telnet | Add-Member -MemberType 'ScriptMethod' -Name 'SendCommand' -Value ({
Param([string]$CommandText)
#$this.Writer.WriteLine($CommandText + $this.EndOfCommand) #writeline should stick the line endings in place anyway, but just to be sure, added this
$this.Writer.WriteLine($CommandText)
(New-Object -TypeName 'PSObject' -Property #{Direction='Input'; Value=$CommandText; When=((Get-Date).ToUniversalTime())})
})
$telnet | Add-Member -MemberType 'ScriptMethod' -Name 'HandleIac' -Value ({
if ($this.Available) {
[int]$byte = $this.GetStream().ReadByte()
[byte]$defaultResponse = 254 # for most IAC requests, we'll respond with don't
switch ($byte) {
-1 { # end of stream (shouldn't happen, but handled in case)
Write-Warning 'Unexpected end of stream whilst processing IAC'
return
}
255 { # Escaped IAC character
Write-Debug 'IAC Escaped'
return $byte
}
253 { #if we get a DO, change default response to WON'T instead of DON'T
$defaultResponse = 252
# do not break; continue to next case statement
}
{(251, 252, 253, 254) -contains $_} { # Will, Won't, Do, Don't
$byte = $this.GetStream().ReadByte() # this is the option we need to respond to; currently we just deny all options to get a raw NVT
switch ($byte) {
-1 {
Write-Warning 'Unexpected end of stream whilst processing IAC'
}
# if we want to handle specific IAC codes we can add support here
default {
$this.GetStream().WriteByte(255) # IAC
$this.GetStream().WriteByte($defaultResponse) # Don't/Won't
$this.GetStream().WriteByte($byte) # whatever you told me
}
}
return
}
default {
Write-Warning "$byte is not a control character, but was received after an IAC character"
}
}
}
})
$telnet | Add-Member -MemberType 'ScriptMethod' -Name 'GetBytes' -Value ({
Start-Sleep -Milliseconds 500 #added to get correct output; otherwise we seem to fly past the handshake :/
while ($this.Available -gt 0) {
[int]$byte = $this.GetStream().ReadByte() #held as int to allow -1 status code for end of stream
switch ($byte) {
-1 { # end of stream
return
}
255 { #IAC control character received
Write-Verbose 'IAC Command Received'
$this.HandleIac()
break
}
{($_ -ge 0) -and ($_ -lt 255)} { # normal data (not sure if it's worth returning the 0s... haven't seen anything to suggest that they're special though, as -1 is the eof.
[byte]$byte
Write-Debug "found $byte"
break
}
default {
throw "Received value $_ when expecting a byte (0-255)"
}
}
}
})
$telnet | Add-Member -MemberType 'ScriptMethod' -Name 'GetOutput' -Value ({
[byte[]]$bytes = $this.GetBytes()
if (($null -ne $bytes) -and ($bytes.Length -gt 0)) {
Write-Verbose "raw output is $(($bytes | %{"$_"}) -join ', ')"
$this.Encoding.GetString($bytes)
} else {
write-verbose 'no output this time'
}
})
$telnet | Add-Member -MemberType 'ScriptMethod' -Name 'ReceiveThenSendCommands' -Value ({
Param([string[]]$Commands)
foreach ($commandText in $commands) {
$this.GetOutput()
$this.SendCommand($commandText)
}
$this.GetOutput()
})
if ($telnet.Connected) {
$telnet
} else {
throw 'Failed to connect'
}
} catch {
Remove-TelnetClient -TelnetClient $telnet
}
}
Function Remove-TelnetClient {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[AllowNull()]
[PSObject]$TelnetClient
)
if ($null -ne $TelnetClient) {
if ($TelnetClient.Connected) {
$TelnetClient.GetStream().Close()
$TelnetClient.Close()
}
if($TelnetClient.Dispose) {
$TelnetClient.Dispose()
}
}
}
Here's an example of how it would be used in a normal scripted session:
# Example Usage
$telnet = New-TelnetClient -ComputerName 'TelnetServerNameFqdnOrIp'
try {
$telnet.ReceiveThenSendCommands(#(
'myTelnetUsername'
'myPlaintextTelnetPassword'
'DIR' #or whatever command I want to run
)) | Format-List # show the output in a readable format, including when it contains new line characters
} finally {
Remove-TelnetClient $telnet
}
But if you wanted to run it in interactive mode, just call SendCommand when you want to push commands to the server, and GetOutput to see results; e.g. you can run each line below one at a time.
$telnet = New-TelnetClient -ComputerName 'TelnetServerNameFqdnOrIp'
$telnet.GetOutput() # will probably display a welcome message & logon prompt
$telnet.SendCommand('myUsername') # send your username
$telnet.GetOutput() # will probably echo back your username then prompt for a password
$telnet.SendCommand('myPassword') # send your password
$telnet.GetOutput() # unlikely to output anything for a valid password; will give an error for an invalid one
$telnet.SendCommand('DIR') # send whatever commands you want to run
$telnet.GetOutput() # get the output of those commands
Remove-TelnetClient $telnet # once you're done, cleanly closes down the client
I have created a powershell script to telnet multiple stores from single host and has options to capture or no capture the tracert and ping command
Command to telnet multiple host and capture tracert and ping command
#Mutlple Telneting guide
#Created by : Mohit
#How to use ?
#Step 1 : Add mutiple IPs in DestinationIP.csv
#Step 2 : Run Batch file TelnetMultipleHost.bat
####################################################################################################################
$DestinationIP= Get-Content .\DestinationIP.csv
$ipV4 = (Test-Connection -ComputerName (hostname) -Count 1).IPV4Address.IPAddressToString
####################################################################################################################
write-host "-------------------Welcome to Multiple Telnet Host Panel-------------------------"
write-host ""
write-host ""
write-host "IMPORTANT: Make sure you are running this tool from source IP which in this case is " $ipV4
write-host ""
$Ports = Read-Host -Prompt "Enter Destination Port No.(# for multple ports just seperate ports with ,)"
write-host ""
write-host "Port No. you entered:" $Ports
write-host ""
write-host "Select Option"
write-host ""
write-host "Type 1 for telnet Host WITH trace logs"
write-host "Type 2 for telnet Host WITHOUT trace logs"
write-host ""
$option =Read-Host -Prompt "Type here"
write-host ""
Start-Transcript -Path .\TraceLogs_$ipV4.txt
switch($option)
{
#Type 1 for telnet Host WITH trace logs
1{
foreach ($Destination in $DestinationIP)
{
foreach ($Port in $Ports) {
# Create a Net.Sockets.TcpClient object to use for # checking for open TCP ports.
$Socket = New-Object Net.Sockets.TcpClient
# Suppress error messages
$ErrorActionPreference = 'SilentlyContinue'
# Try to connect
$Socket.Connect($Destination, $Port)
# Make error messages visible again
$ErrorActionPreference = 'Continue'
# Determine if we are connected.
if ($Socket.Connected) {
"${Destination}: Port $Port is open"
$Socket.Close()
}
else {
"${Destination}: Port $Port is closed or filtered"
if (test-connection $Destination -count 1 -quiet) {
write-host $Destination "Ping succeeded." -foreground green
} else {
write-host $Destination "Ping failed." -foreground red
}
Test-NetConnection $Destination -TraceRoute
}
# Apparently resetting the variable between iterations is necessary.
$Socket = $null
}
}
}
# Type 2 for telnet Host WITHOUT trace logs
2{
foreach ($Destination in $DestinationIP) {
foreach ($Port in $Ports) {
# Create a Net.Sockets.TcpClient object to use for
# checking for open TCP ports.
$Socket = New-Object Net.Sockets.TcpClient
# Suppress error messages
$ErrorActionPreference = 'SilentlyContinue'
# Try to connect
$Socket.Connect($Destination, $Port)
# Make error messages visible again
$ErrorActionPreference = 'Continue'
# Determine if we are connected.
if ($Socket.Connected) {
"${Destination}: Port $Port is open"
$Socket.Close()
}
else {
"${Destination}: Port $Port is closed or filtered"
}
# Apparently resetting the variable between iterations is necessary.
$Socket = $null
}
}
}
}
Stop-Transcript
Please note: TelnetMultipleHost.bat this batch is used to run the powershell command
Make sure we have bat, ps1 file in same directory
Code for batch file:
#echo off
Powershell.exe -executionpolicy remotesigned -File .\TelnetMultipleHost.ps1
pause
i use below script for telnet to multiple ip's:
$server_list = #('1.1.1.1:443', '10.100.8.22:3389', '10.100.8.21:22')
Foreach ($t in $server_list)
{
$source = $t.Split(':')[0]
$port = $t.Split(':')[1]
Write-Host "Connecting to $source on port $port" | Out-File 'output.txt' -Append
try
{
$socket = New-Object System.Net.Sockets.TcpClient($source, $port)
}
catch [Exception]
{
Write-Host $_.Exception.GetType().FullName | Out-File 'output.txt' -Append
Write-Host $_.Exception.Message | Out-File 'output.txt' -Append
}
Write-Host "Connected`n" | Out-File 'output.txt' -Append
}
when you are connected to ip's script show you you are connected to