Passing piped commands via SSH - powershell

I'm using the Powershell module Posh-SSH to ssh into an Ubuntu server and run commands. I'm not having any difficulty passing simple lines such as:
$sshSession = New-SSHSession -ComputerName server001 -Credential $credential
$sshStream = New-SSHShellStream -Index $sshSession.sessionID
$sshStream.WriteLine("history")
$sshStream.Read()
The last line outputs exactly what it's supposed to. I want to run the following on the server:
for guest in `nova list --all-tenants --host serverName | grep Shutdown | awk '{ print $2 }'`; do nova start $guest; sleep 5; done"
Pasting this line right into $sshStream.WriteLine("") doesn't work at all as ` is an escape character in Powershell and $'s are used for variables already. I attempted to work around this by escaping some characters and putting it into a variable:
$block = "for guest in ``nova list --all-tenants --host server001 | grep Shutdown | awk '{ print `$2 }'`; do nova start `$guest; sleep 5; done"
$sshStream.WriteLine("$block")
$sshStream.WriteLine($block)
Both of my attempts above do not get read properly on the server. Any idea how I can work around this or if there's a better way to do this?
Thanks in advance

As TessellatingHeckler suggested, I used single-quotes and it worked:
$block = 'for guest in `nova list --all-tenants | grep Shutdown | awk ''{ print $2 }''`; do nova stop $guest; done'
$sshStream.WriteLine($block)

Try this
$block = #"
for guest in ``nova list --all-tenants --host server001 | grep Shutdown | awk '{ print `$2 }'`; do nova start `$guest; sleep 5; done
"#
$sshStream.WriteLine($block)
It will treat the "block" as a literal string (i.e. no escaping).

Related

Filename on Windows that works with pipes in batch or PowerShell

I have a Linux shell script with a line that looks like this:
./cmd1 -o /dev/stdout | ./cmd2
I'm trying to port it to Windows. I don't care whether it ends up as a batch file or a PowerShell script. I tried this in a batch file at first:
cmd1 -o con | cmd2
But this sent the output of cmd1 to the console, and cmd2 didn't receive any input. The problem is that con is the equivalent of /dev/tty, not /dev/stdout. What output filename can I pass that will be the equivalent of /dev/stdout, or at least close enough to make the pipe work?
Can you try this?
./cmd1 -o /dev/stdout | ./cmd2 -i /dev/stdin
The equivalent command on Windows using batch or PowerShell would be:
cmd1 -o - | cmd2 -i -
you can pipe it to foreach-object
cmd1 | % { cmd2 $_ }
the $_ stands for the result of the output that is sent to the next command, sometimes with certain programs you may need to use write-output (cmd1) | % {cmd2} but the first one should suffice for what you want to do.
You can also store the output of the first command like this
$var = cmd1
or like this
cmd1 > $var
If you can specify the file output and file input for both commands you could try this.
$file = "C:\file"
cmd1 -o $file ; cmd2 -i $file
but as phuclv commented, there's no stdout equivalents in Windows by default

Powershell - Select a specific word from text

I'm still very much learning Powershell and I'm a bit lost regarding a command to use.
I have this
docker login -u AWS -p PASSWORD -e none number.dkr.ecr.REGION.amazonaws.com
and I want to select PASSWORD.
Ideally, I would like this PASSWORD to get into a file, and use it after (but that I can do).
I am lost on what command to use. I know awk '{print $6}' would work but I need the powershell as I'm using a windows machine.
I know it's a really simple question, I have been reading answers, but I am just confused by the different parameters, and the different ways by different people and well, Powershell is wonderful but I'm still learning.
Thanks a lot!!
The default field separator(s) in awk is whitespace, so we can do the same in PowerShell and then grab the 6th resulting substring.
For this, we can use the -split regex operator - it supports the following syntaxes:
[strings] -split [pattern]
or
-split [strings]
When used as in the second example above, it defaults to splitting on whitespace just like awk:
-split "docker login -u AWS -p PASSWORD -e none number.dkr.ecr.REGION.amazonaws.com"
If we wrap the expression in #(), we can index into it:
$Password = #(
-split "docker login -u AWS -p PASSWORD -e none number.dkr.ecr.REGION.amazonaws.com"
)[5]
or we can use the Select-Object cmdlet to grab it:
$Password = -split "docker login -u AWS -p PASSWORD -e none number.dkr.ecr.REGION.amazonaws.com" |Select-Object -Index 5
If, however, we always want to grab the substring immediately after -p instead of whatever the 6th string is, we could use the -replace regex operator instead:
$string = "docker login -u AWS -p PASSWORD -e none number.dkr.ecr.REGION.amazonaws.com"
$string -replace '^.*-p\s+(\S+).*$','$1'
You can make this more complex :) I just used simple regexp, but you can make it better :)
$x = "docker login -u AWS -p PApSSWORD -e none number.dkr.ecr.REGION.amazonaws.com "
$x -match "(\-p[a-z0-9 ]*\-e)"
$matches[1] -replace '(\-p) ([a-z0-9 ]*) (\-e)' , ' $2'
Another solution using a Regular Expression,
here with lookarounds
$string = 'docker login -u AWS -p PASSWORD -e none number.dkr.ecr.REGION.amazonaws.com'
if ($string -match '(?<=-p\s+).*(?=\s+-e)'){
$Password = $Matches.Value
} else {
"No password found"
}

invoke-expression doesnot throw an error in powershell

I am creating custom forms and adding it to the printer server properties using forms.vbs and running it through cmd. The script is as follows
cscript 'C:\Tools\forms.vbs' -a -n "DD" -u inches -h 7.48 -w 7.48 -t 0 -e 0 -b 7.48 -r 7.48
This works fine when running in command prompt.
Now I invoked this code to the powershell as follows and it also works fine
$formname = "DD"
$cmd = "cscript 'C:\Tools\forms.vbs' -a -n " + '"' + $formname + '"' + " -u inches -h 7.48 -w 7.48 -t 0 -e 0 -b 7.48 -r 7.48 "
Invoke-Expression $cmd
The issue started when I thought of checking the error handling thing for the powershell invoke expression.
In cmd when we give the expression as
cscript 'C:\Tools\forms.vbs' -a -n "DD" -u inches -h 7.48 -w 7.48 -t 0 -e 0 -b 0 -r 0
This will definitely throw error as per the notes given in the form.vbs and it will not create a form.
So when I invoked the same error-thrown script to my powershell, the form is not creating as well as it is not throwing any errors. So I request me to guide in this regard. Thanks in advance.
Invoke-Expression only checks if it's possible to run the command at all. For example, if cscript.exe cannot be found, Invoke-Expression will throw an ObjectNotFound exception.
It doesn't check the exit code of the command or in any way parse its output. You should be able to see the output however.
Make sure you don't mix single and double quotes inside your expression:
$formname = "DD"
# Note double quotes around C:\Tools\forms.vbs
$cmd = 'cscript "C:\Tools\forms.vbs" -a -n ' + '"' + $formname + '"' `
+ ' -u inches -h 7.48 -w 7.48 -t 0 -e 0 -b 7.48 -r 7.48 '
Invoke-Expression $cmd
Output:
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.
Unable to add form DD, error code: 0x1A8. Object required
If you want your code to throw an exception, you need to parse the output manually, e.g.:
try {
$output = Invoke-Expression $cmd
if ($output -like "*error code*") { throw $output }
}
catch [System.Exception] {
$message = ($Error[0].Exception -split [System.Environment]::NewLine)[-1]
$message
}
Output:
Unable to add form DD, error code: 0x1A8. Object required
I have been using Start-Process to manage installs and uninstalls, and recently started using the same approach for arbitrary executables, which seems to be somewhat what you are trying to do, so...
$new.ExitCode > $null
$filepath = 'cscript.exe'
$argumentList = "C:\Forms.vbs"
$exitCode = (Start-Process -FilePath:$filePath -argumentList:$argumentList -wait -errorAction:Stop -PassThru).ExitCode
$exitCode
The VBS just throws up a messagebox and quits with a return code, like so.
MsgBox "Text"
WScript.Quit 4
After I close the message box I get that 4 back at the PowerShell console. With no Wscript.Quit, or no exit code provided, I get the expected 0 back.
Simple example, but maybe gets you close, assuming you can get the error code you need into a variable so you can return it from the VBS. Or maybe someone points out some nuance I am not aware of and we both learn something. ;)

grep: can't open "command"

I am logging in to the remote machine and executing a command and then grepping the result. I am using Expect module. Here is my sample code.
use Expect;
my $exp=new Expect();
$exp->spawn("ssh $hostname\r");
$exp->expect(5,"*]-> ");
$exp->send("command sent here \r");
$exp->expect(5,"*]-> ");
my $res=$exp->before(); // Here i ll get the command output in a variable. The variable contains TCPIP:1.1.1.1 in one line and UDPIP:1.2.2.2 in another line.
my $id=`grep -i TCPIP $res | cut -d ":" -f2 `;
print " The result is $id \n";
But here i am getting an error
grep: can't open "command sent " .sh: TCPIP not found sh:UDPIP not found.
Maybe you can do even without expect?
my $id = `ssh $hostname <your_command> | grep -i TCPIP | cut -d":" -f2`;
grep takes a filename as paramater not a string. You need something like this:
echo $res | grep -i TCPIP | cut -d ":" -f2 `;
Make sure the command works manually via ssh first though.
But consider using perl itself do do the match and cut rather than spawning out to grep, that would be better.

Pulling hostname from TNS entry

I am working on a script that will need to determine which node a db being used by a local app is running on. I've been trying to use this as a chance to force myself to learn awk/sed and have a test script to test the statements. It's working off a copy of the tnsnames.ora file I have moved to the home folder the script is located in.
Here is a valid tnsnames.ora stanza:
(
DESCRIPTION = (
ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP) (Host=iplab)(Port=1521))
)
(CONNECT_DATA=(SID=spurs1))
)
After doing some research and getting the awk expression to pull the tns entry to $host I came up with the below script but it doesn't seem to work.
#!/bin/ksh
db=spurs
host=$(awk -v db=$db "/${db}/ {for(i=1; i<=5; i++) {getline; print}}" tnsnames.ora)
echo $host
host= $host | sed 's/Host\s=\s\([a-z]+[0-9]?\)/\1/'
echo $host
When I run it the awk statement I get the following:
(DESCRIPTION = (ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP) (Host=hostname)(Port=1521))) (CONNECT_DATA=(SID=spurs1)) )
./tns.ksh: line 6: (DESCRIPTION: not found
(DESCRIPTION = (ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP) (Host=hostname)(Port=1521))) (CONNECT_DATA=(SID=spurs1)) )
From what I have seen reading tutorials and forums I think sed is setup correctly and should be setting $host to one or more lowercase letters followed by 0 or 1 numbers after Host = . Since (DESCRIPTION is the start of $host before and after the sed statement I'm not sure how it isn't finding it, an
This worked for me:
tnsping $db | grep HOST | cut -d\ -f 14 | sed 's/).*//g'
On my system I can use this to get the host as long as the host name doesn't have an equals sign (or the actual literal word HOST in the name of the host):
echo $TNS_STRING | sed 's/.HOST//g' | sed 's/).//g' | sed 's/=//g' | sed 's/\s*//g'
Your value for $host is likely a multiline value, so you need to quote it anyplace you use it, i.e.
host=$(awk -v db=$db "/${db}/ {for(i=1; i<=5; i++) {getline; print}}" tnsnames.ora)
echo "$host"
You also need to capture the output (using command-substitution) via $(...)
host=$(echo "$host" | sed 's/Host\s=\s\([a-z]+[0-9]?\)/\1/')
echo "$host"
(and echo it), so it can be processed by sed
Revise
host=$(echo $host | sed 's/.*Host=//; s/).*$//)
echo "$host"
I've switched back to just $host, without the dbl-quotes, as you don't want the linebreaks in the data. Now it is all one big string, and the regex, strips every upto host=, and then strips everything after the first remaining ) char.
If you still get error messages, I don't have access to a tnsnames.ora record, so please edit your query to include a valid record.
I hope this helps.
you may be better relying on the output of tnsping instead of parsing the file: tnsping appears to emit the description on one line:
host=$(
tnsping $db | while read line; do
if [[ "$line" == *HOST* ]]; then
s=${line#*HOST=}; s=${s%%)*}; echo "$s"; break
fi
done
)
This might work for you:
db=spurs
host=$(sed '/^(/,/^)/!d;/^(/{h;d};H;/^)/!d;g;/'"$db"'/!d;s/.*Host=\([^)]*\).*/\1/' tnsnames.ora)
Tested Code:
OIFS=$IFS;
IFS="(";
tns=`tnsping TNS_ALIAS`
tns_arr=($tns);
tns_info=(`(for ((i=0; i<${#tns_arr[#]}; ++i)); do echo "${tns_arr[$i]/)/}"; done)| grep 'HOST\|PORT'|sed 's/)//g'|sed 's/ //g'`)
for ((i=0; i<${#tns_info[#]}; ++i)); do eval "export ${tns_info[$i]}"; done
echo "host:" $HOST
echo "port:" $PORT
IFS=$OIFS;