I need to send a list of commands to several devices. For each IP, open an SSH-connection with the given credentials from User and Password textboxes, run all of the commands and return the output to the Output textbox.
Normally I'd use
plink.exe -ssh admin#172.16.17.18 -pw PassW0rd "command"
Unfortunately, the remote host does not let me do that:
Sent password
Access granted
Opening session as main channel
Opened main channel
Server refused to start a shell/command
FATAL ERROR: Server refused to start a shell/command
However, if I connect without handing over a command:
Sent password
Access granted
Opening session as main channel
Opened main channel
Allocated pty (ospeed 38400bps, ispeed 38400bps)
Started a shell/command
Welcome to XXX
System_Name>#
Now, I can type my commands and have them executed. I tried PoshSSH, which lets me connect but any command times out.
I broke down the lines from the IP- and Command-boxes into string-arrays and made for loops. Then I tried several approaches with Start-Job and SystemDiagnostics.Process* without success.
Now I'm a bit clueless and would appreciate any help:
for ($a=0; $a -lt $IPArray.Length; $a++){
# Open an interactive Session with plink like
# plink.exe -ssh ' + $User + '#' + $IPArray[$a] + ' -pw ' + $Passwd
for ($b=0; $b -lt $CommandArray.Length; $b++){
# Send $CommandArray[$b] to plink-Session
# Wait for command to finish
# Read output and send it to the textbox
}
}
Edit: Thanks to Martin Prikryl's answer I'm a step further:
for ($a=0; $a -lt $IPArray.Length; $a++){
$User = $UserTextBox.text
$IP = $IPArray[$a]
# $Passwd = $PwdTextBox.Text
$Outputtext= $Outputtext + "~~~~~ " + $IP + " ~~~~~" + "`r`n"
$isSession = New-SSHSession -ComputerName $IP -Credential $User
$isStream = $isSession.Session.CreateShellStream("PS-SSH", 0, 0, 0, 0, 1000)
for ($b=0; $b -lt $CommandArray.Length; $b++){
$Command = $CommandArray[$b]
$isStream.WriteLine("$Command")
Start-Sleep -Seconds 1
}
$isReturn = $isStream.Read()
$Outputtext= $Outputtext + $isReturn + "`r`n"
$outputBox.Text = $Outputtext
}
returns:
~~~~~ 172.16.17.18 ~~~~~
Welcome to XXX
System_18>#echo 1
1
System_18>#echo 2
2
System_18>#ping 8.8.8.8
PING 8.8.8.8 56 data bytes
~~~~~ 172.16.17.19 ~~~~~
Welcome to XXX
System_19>#echo 1
1
System_19>#echo 2
2
System_19>#ping 8.8.8.8
PING 8.8.8.8 56 data bytes
~~~~~ 172.16.17.20 ~~~~~
Welcome to XXX
System_20>#echo 1
1
System_20>#echo 2
2
System_20>#ping 8.8.8.8
PING 8.8.8.8 56 data bytes
Now I need to achieve two things:
Get the credentials from the corresponding input fields (Currently, I need to type in the password once for each IP)
Done:
$User = $UserTextBox.text
$Passwd = ConvertTo-SecureString $PwdTextBox.Text -AsPlainText -Force
$SSHCred = New-Object System.Management.Automation.PSCredential -ArgumentList ($User, $Passwd)
# ...
$isSession = New-SSHSession -ComputerName $IP -Credential $SSHCred
Make it wait for a command to finish, before sending the next one. (Currently, it just waits 1 second)
However, I'm happy that the remote hosts now talk to me, at least.
Thank you.
Should I open new questions, if I need further help with the script or continue to log the progress, here?
You are obviously connecting to some "device", not a full server. Embedded SSH implementations usually do not implement SSH "exec" channel. You have to use "shell" channel (what is otherwise not recommended for command automation).
With Plink you can achieve that by writing the "command" to Plink input, instead of using the -m switch.
See also Executing command using Plink does not work, but does in PuTTY.
Though you should not run an external application to implement SSH. Use a native .NET SSH implementation, like SSH.NET. Pseudo code to execute command with SSH.NET in "shell" channel:
var client = new SshClient(...);
client.Connect();
var shell = client.CreateShellStream(...);
shell.WriteLine("command");
var output = shell.Read();
The "shell" channel is a black box with an input and an output. There is no reliable way to use it to execute a command, read its output, and execute other commands. You have no API to tell when a command execution has ended. That is why I wrote above that the "shell" channel is not recommended for command automation. All you can do is to parse the shell output and look for your device's command prompt: System_20>.
Related
I'm writing a unix script to check for database connectivity in a server. When my database connection gets errored out or when there is delay observed in connecting to the database, I want the output as "Not connected". In case it gets connected, my output should be "Connected". It is a Oracle databse.
When there is delay in database connectivity, my code is not working and my script gets hung. What changes should I make in my code so that it is able to handle both the conditions(when I get an error connecting to the database and when there is delay observed in connecting to the database)??
if sqlplus $DB_USER/$DB_PASS#$DB_INSTANCE< /dev/null | grep 'Connected to'; then
echo "Connectivity is OK"
else
echo "No Connectivity"
fi
The first thing to add to your code is a timeout. Checking database connectivity is not easy and there can be all kinds of problems in the various layers that your connection passes. A timeout gives you the option to break out of a hanging session and continue the task with reporting that the connection failed.
googleFu gave me a few nice examples:
Timeout a command in bash without unnecessary delay
If you are using Linux, you can use the timeout command to do what you want. So the following will have three outcomes, setting the variable RC as follows:
"Connected to" successful: RC set to 0
"Connected to" not found: RC set to 1
sqlplus command timed out after 5 minutes: RC set to 124
WAIT_MINUTES=5
SP_OUTPUT=$(timeout ${WAIT_MINUTES}m sqlplus $DB_USER/$DB_PASS#$DB_INSTANCE < /dev/null )
CMD_RC=$?
if [ $CMD_RC -eq 124 ]
then
ERR_MSG="Connection attempt timed out after $WAIT_MINUES minutes"
RC=$CMD_RC
else
echo $SP_OUTPUT | grep -q 'Connected to'
GREP_RC=$?
if [ $GREP_RC -eq 0 ]
then
echo "Connectivity is OK"
RC=0
else
ERR_MSG="Connectivity or user information is bad"
RC=1
fi
fi
if [ $RC -gt 0 ]
then
# Add code to send email with subject of $ERR_MSG and body of $SP_OUTPUT
echo Need to email someone about $ERR_MSG
fi
exit $RC
I'm sure there are several improvements to this, but this will get you started.
Briefly, we use the timeout command to wait the specified time for the sqlplus command to run. I separated out the grep as a separate command to allow the use of timeout and to allow more flexibility in checking additional text messages.
There are several examples on StackOverflow on sending email from a Linux script.
I am trying to make a batch file which connects to my router via SSH and executes some commands. The batch file currently looks like:
ssh user#192.168.1.1
configure
etc.....
When I run the batch file the first line executes, and then the command window asks
user#192.168.1.1's password:
After inputting the password, the second line does not execute (I imagine this is obvious to someone who knows what they're doing)
I'm looking for a solution that either lets me manually enter the password and then lets the script continue, or one where the script enters the password itself.
Try this (doesn't need any 3rd party tools):
$env:TMPPW=Get-Content -Path 'secure_file.txt'
$j=Start-Job -ScriptBlock{Start-Sleep -Seconds 1;
(New-Object -ComObject wscript.shell).SendKeys("$env:TMPPW{ENTER}")}
Get-Content -Path 'MyScript.txt' | & ssh.exe -q -4 -l user 192.168.1.1
$env:TMPPW=([guid]::NewGuid()).Guid ; $env:TMPPW=$null
I try to run this PowerShell script to get counter.
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[String]
$DatabaseName
)
$ErrorActionPreference = "Stop"
$VerbosePreference = "Continue"
cls
Write-Output "Collecting counters..."
Write-Output "Press Ctrl+C to exit."
$counters = #("\Processor(_Total)\% Processor Time", "\LogicalDisk(C:)\Disk Reads/sec", "\LogicalDisk(C:)\Disk Writes/sec",
"\LogicalDisk(C:)\Disk Read Bytes/sec", "\LogicalDisk(C:)\Disk Write Bytes/sec", "\SQLServer:Databases($DatabaseName)\Log Bytes Flushed/sec")
Get-Counter -Counter $counters -SampleInterval 1 -MaxSamples 3600 |
Export-Counter -FileFormat csv -Path "C:\sql-perfmon-log.csv" -Force
But I got error when ran the script.
Error on the screen.
I found out that error occur in "\SQLServer:Databases($DatabaseName)\Log Bytes Flushed/sec"
Can somebody prompt me How I can enter a specific instance probably with credentials?
I see you're trying to run a powershell script from a command prompt.
Make sure you're doing this on the machine your SQL databases are hosted on.
You need to either open powershell administratively on your computer, or if you already have an administrative command prompt open, type "powershell" into it.
You can either paste all the code into your current host after specifying a string for $DatabaseName or just dot-source . the script and pass $DatabaseName as a parameter into the script.
If you're not sure what any of that means, take a look at
https://technet.microsoft.com/en-us/library/dn425048.aspx for more information on getting started with using powershell.
I am in the middle of moving to the cloud, migrating from SBS 2003 Active Directory, to 2008 R2.
I configured a new user, and noticed that the user was unable to reset their password.
My Server Admin showed me how to use net user.
I noticed that I can obtain information from some accounts and not others. With over 100 accounts to process, I thought I'd try PowerShell.
In this post (Use powershell to look up 'net user' on other domains?) Lorenzo recommends using Get-ADUser (and this applies to polling from another domain). When I run Get-ADUser from my PowerShell prompt, I receive a message stating that the commandlet is not recognized.
I am reading the user IDs from a text file, and sending the output to a log file so that I can send to the server admin for further analysis.
Here is my code so far (please note that I am completely new to PowerShell):
# Get our list of user names from the local staff.txt file
$users = get-content 'C:\Scripts\staff.txt'
# Create log file of output:
$LogTime = Get-Date -Format 'MM-dd-yyyy_hh-mm-ss'
$CompPath = "C:\Scripts\"
$CompLog = $CompPath + "NetUserInfo" + $LogTime + ".txt"
New-Item -path $CompLog -type File
foreach ($user in $users) {
#Testing user:
"Testing user: $user" | out-file $CompLog -Append
# Obtain user information using net user:
net user $user /domain >> $CompLog
# Pause to let system gather information:
Start-Sleep -Second 15
}
As the script runs currently, my log file will have two or three user names followed by the response "The request will be processed at a domain controller for domain (domain)"
If net user, from CMD, would return "System error 5 has occurred, Access is denied." This is not logged in the output file. IF net user, from CMD, would return user information, this is logged to the output file. I am currently receiving output for only a couple users, but when I run the command from CMD, I am able to retrieve information for at least ten.
My first thought was that I needed to wait for the net user command to complete (hence the Start-Sleep command) but that has not had any effect on the output.
Any assistance would be greatly appreciated.
Sorry, I don't have enough reputation to add a comment.
When you run programs in Powershell (such as net user... or ping) it should be running exactly the same as it would in the normal command prompt (cmd).
If I understand correctly you're getting different results when you (effectively) run the same thing in Powershell or Command Prompt. Is that right?
Are you using the ISE to build your script? If so you can set Breakpoints that will pause the script and allow you to see what the variables are. It could be that the $user variable isn't holding what you think it should be, or that the value doesn't match the name for a domain user account.
EDIT:
What happens when you run the net user ... command interactively (e.g. not as a script, but manually) in Powershell? Do you get an error? If so, what does the error say?
Additional related stuff:
You shouldn't need the Start-Sleep as the commands are run in order, and the next line shouldn't execute until the previous on has completed.
Also, which version of Powershell are you using?
(You can check by running $host.version.Major)
The Get-ADUser cmdlet requires version 3 (I believe) and also needs to import the Active Directory module.
The reason the error output is not being appended to the log file is because you are only redirecting the STDOUT (standard output stream). To also redirect the STDERR (standard error stream) change
net user $user /domain >> $CompLog
to
net user $user /domain 2>&1 $CompLog
This explains it a bit more:
http://www.techotopia.com/index.php/Windows_PowerShell_1.0_Pipes_and_Redirection#Windows_PowerShell_Redirection_Operators
I am running Jenkins on a virtual machine and calling the cli from a batch file to do a safe shutdown like so:
java -jar $JenkinsCLILocation -s http://$JenkinsURL safe-shutdown
Once it's finished shutting down, I'm turning off the virtual machine and cloning it. However, I don't want to turn off the virtual machine before the safe shutdown of Jenkins is complete. Is there any way I can monitor the Jenkins using either the cli or a batch command to see if it's properly shut down?
This is the most ghetto hack. What I'm basically doing is looking at the error output from the java command that executes the Jenkins CLI jar file, and if the error output contains "Failed to connect", I assume that the Jenkins has been shut off.
I would love to see if anyone has a more graceful solution, cause man, this is just gross.
while(!$JenkinsIsDown)
{
Write-Host "Getting the session ID from Jenkins to check if it's down..."
#This is a terrible hack so we can process the output of the command
$JenkinsProcess = New-Object System.Diagnostics.ProcessStartInfo
$JenkinsProcess.FileName = "java"
$JenkinsProcess.RedirectStandardError = $true
$JenkinsProcess.RedirectStandardOutput = $true
$JenkinsProcess.UseShellExecute = $false
$JenkinsProcess.Arguments = "-jar " + $JenkinsCLILocation + " -s http://" + $JenkinsURL + " session-id"
$JenkinsProcess.WaitForExit()
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $JenkinsProcess
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
if($stderr.Contains("Failed to connect"))
{
Write-Host "The Jenkins is down."
$JenkinsIsDown = $true
}
}