Execute multiple shell/command prompt commands in same instance - python

I am looking for a way to execute multiple commands in the same shell instance using a separate function for each, something that I can define when the shell process opens/closes and can pass commands to.
so far all the answers I have found have only been in a single function
ie:
#!/usr/bin/env python
from subprocess import check_call
check_call(r"""set -e
ls -l
<some command> # This will change the present working directory
launchMyApp""", shell=True)
I need the same effect but with each command in a different function like
shell.open()
shell.exec("dir")
shell.exec("cd C:/Users/" + User + "/Desktop)
shell.close()
if you are wondering whyyy it has to be separate the command to run is coming from user input. yes I realize that is a security risk, but security isn't a problem in this case, as its purely an educational venture and not going to be used for anything

you could use subprocess.check_call(cmds_str, shell=True) in conjunction with multiple commands in the same line: How to run two commands in one line in Windows CMD?
You could build each command individually and add them to a list, and then use ' & '.join(cmd_list) to get cmds_str.

I don't use Windows but it works on Linux.
You can try pexpect with cmd.exe
import pexpect
child = pexpect.spawn("cmd.exe")
child.expect_exact("> ")
#print(child.before.decode('utf-8'))
print(child.before)
child.sendline("dir")
child.expect_exact("> ")
print(child.before)
child.sendline("cd C:/Users/" + User + "/Desktop")
child.expect_exact("> ")
print(child.before)
It runs cmd.exe, sends command in child.sendline() and looks for prompt child.expect_exact("> ") to get all text generated by command child.before.

Related

How to pass arguments to terminal?

I have a python script that does a zipping of a few files. For zipping the files with a password I use os.system() function and pass my zipping command ie. zip -j -re <file_path> <to_path>. This command asks for a password on the terminal which I have to put manually on the terminal like this.
Enter password:
Verify password:
I want to hardcode my password and pass it when the terminal asks for a password.
I tried to send the passwords using os.system(password) twice thinking that it will take it but it did not work.
cmd = "zip -j -re {} {}".format(DUMPSPATH,TEMPPATH)
passwrd = 'hello123'
os.system(cmd)
os.system(passwrd)
os.system(passwrd)
How do I send arguments using python to fill passwords automatically?
When you call os.system(), you are sending a command to your shell to run. So calling os.system(passwrd) is sending your password as a standalone command to be run by the shell, which is not what you want here.
The zip program features a -P password argument that allows you to specify the password to encrypt zip files when you initially run the command without having to manually input it for batch jobs. So essentially your code should be changed to something like this:
passwrd = 'hello123'
cmd = "zip -j -re -P {} {} {}".format(passwrd, DUMPSPATH,TEMPPATH)
os.system(cmd)
Another side note, it's recommended to use the subprocess module instead of resorting to os.system, so take a look at subprocess.Popen if you get a chance.
It's nice computer_geek has found the correct command line argument (I can't see it listed over here, but it works), but I absolutely HAVE to post my ludicrous solution (for posterity). NOTE - requires xdotool installed on your system.
import subprocess
DUMPSPATH = ""
TEMPPATH = ""
pwd = "lala"
zipper = subprocess.Popen(['zip','-j','-re',DUMPSPATH,TEMPPATH])
subprocess.run(['xdotool','type',pwd])
subprocess.run(['xdotool','key', 'Return'])
subprocess.run(['xdotool','type',pwd])
subprocess.run(['xdotool','key', 'Return'])
zipper.wait()

How to make a python cmd module command that runs python code

So I'm messing around with the "cmd" module for python, I want a command where you can type "python" and then it opens a python command line. Sort of like how an actual command line would.
Here's my current code.
import cmd
class pythonCmd(cmd.Cmd):
def do_(self, args): # <--- I want this command to have it so you don't type a key word
exec(args)
class cmdLine(cmd.Cmd):
def do_python(self, args):
prompt = pythonCmd()
prompt.prompt = 'python> '
prompt.cmdloop('Python 3.8.2')
prompt = cmdLine()
prompt.prompt = '> '
prompt.cmdloop('Command line starting . . .')
I don't know whether you have to use cmd module or not. But there are much better modules similar to cmd. Modules such as subprocess, os and etc.
I recently used subprocess module, try it.
How about this:
Instead of running your program that opens a shell that can take both python commands and potentially your own commands,
Run python shell, import your program module- you have native python shell that can run python code.
Add support of additional commands by implementing a function like cmd(args) which you can call from the shell. You may need to work on your module to simplify using it in interactive python shell by providing #aliases”to existing functions etc..
With do_shell function you can use it with "!" syntax. For example
> !print("Henlo world")
This would print it, you can use other commands too.

Acessing Command prompt using Python for automated flow

I need to run an executable file that works only with command prompt multiple times.It is used to retrieve data Information based on the Details of the framenumbers and it ranges only between 0 to any postive value. And I have this framenumber data in a Excel file and i want it to loop through the range of framenumber. This is the part of command --start {framenumber1} --end {framenumber2} where it should be changed for each execution. i also Need admin rights to run this executable.
Is there any possible way of automating this workflow in python for using the command prompt multiple times with slightly different Input? Any Suggestion would be very helpful
Found the solution of using os.system() invoking to solve it. Here is the gist of code of how to Access the command prompt using python with admin Rights.
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
if is_admin():
df=pd.read_excel(r'D:\final_xxxx.xlsx',sep=',')
for i,x in zip(df['framenumber'],df['C']):
os.chdir(os.path.dirname(os.path.abspath(r'C:\Users\xxxx\Desktop\xxxxxxx\xxxv1.02\xxx\xxx.exe')))
if i==i:
k=math.trunc(i)
j=k
#executable operation running in python for command prompt till the loop ends
os.system(r'xxxx.exe --file "D:\xxxx\2020-07-27_xxxxx.xxxx" --output "D:\xxxx" --start {} --end {}'.format(j,j))
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)

What's the best way to execute PowerShell scripts from Python

All the previous posts on this topic deal with specific challenges for their use case. I thought it would be useful to have a post only dealing with the cleanest way to run PowerShell scripts from Python and ask if anyone has an better solution than what I found.
What seems to be the generally accepted solution to get around PowerShell trying to interpret different control characters in your command differently to what's intended is to feed your Powershell command in using a file:
ps = 'powershell.exe -noprofile'
pscommand = 'Invoke-Command -ComputerName serverx -ScriptBlock {cmd.exe \
/c "dir /b C:\}'
psfile = open(pscmdfile.ps1, 'w')
psfile.write(pscommand)
psfile.close()
full_command_string = ps + ' pscmdfile.ps1'
process = subprocess.Popen(full_command_string , shell=True, \
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
When your python code needs to change the parameters for the Powershell command each time you invoke it you end up writing and deleting a lot of temporary files for subprocess.Popen to run. It works perfectly but it's unnecessary and not very clean. It's really nice to be able to tidy up and wanted to get suggestions on any improvements I could make to the solution I found.
Instead of writing a file to disk containing the PS command create a virtual file using the io module. Assuming that the "date" and "server" strings are being fed in as part of a loop or function that contains this code, not including the imports of course:
import subprocess
import io
from string import Template
raw_shellcmd = 'powershell.exe -noprofile '
--start of loop with server and date variables populated--
raw_pslistcmd = r'Invoke-Command -ComputerName $server -ScriptBlock ' \
r'{cmd.exe /c "dir /b C:\folder\$date"}'
pslistcmd_template = Template(raw_pslistcmd)
pslistcmd = pslistcmd_template.substitute(server=server, date=date)
virtualfilepslistcommand = io.BytesIO(pslistcmd)
shellcmd = raw_shellcmd + virtualfilepslistcommand.read()
process = subprocess.Popen(shellcmd, shell=True, stdout=subprocess.PIPE, \
stderr=subprocess.PIPE)
--end of loop--
Arguably the best approach is to use powershell.exe -Command rather than writing the PowerShell command to a file:
pscommand = 'Invoke-Command ...'
process = subprocess.Popen(['powershell.exe', '-NoProfile', '-Command', '"&{' + pscommand + '}"'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Make sure double quotes in the pscommand string are properly escaped.
Note that shell=True is required only in certain edge cases, and should not be used in your scenario. From the documentation:
On Windows with shell=True, the COMSPEC environment variable specifies the default shell. The only time you need to specify shell=True on Windows is when the command you wish to execute is built into the shell (e.g. dir or copy). You do not need shell=True to run a batch file or console-based executable.
After spending a fair amount of time on this.
I think that running powershell commands from python may not make sense to a lot of people, especially people who work exclusively in windows environments. There are numerous clear advantages to python over powershell however so the ability to do all your business logic in python and then selectively execute powershell on remote servers is truly a great thing.
I've now been through several improvements of my "winrmcntl" module which I can't share due to company policy unfortunately but here is my advice to anyone who would like to do something similar. The module should take as input an unmodified PS command or scriptblock as you'd run it if you were typing directly in PS on the destination box. A few tricks:
To avoid permission difficulties, ensure the user running your python script and hence the one running powershell.exe via process.Popen is the user that has the correct permissions on the windows box you're invoke-command is pointing at. We use an enterprise scheduler which has windows vms as agents on which the python code lives which takes care of that.
You will sometimes rarely but still get the odd esoteric exception from powershell land, if they're anything like the one in particular I saw the odd time, microsoft scratch their heads at a little and get you to do time consuming application stack tracing. This is not only time consuming but very difficult to get right because it's resource intensive and you don't know when the exception will next occur. In my opinion, it's much better and easier to parse the output of the exception and retry up to x number of times if a certain text appears in those exceptions. I keep a list of strings in my winrmcntl module which currently contains a single string.
If you want to not have to "massage" the powershell commands as they traverse the python -> windows -> powershell -> powershell stack to make them work as expected on destination boxes, the most consistent method I've found is to write your one liners and scriptblocks alike into a ps_buffer.ps1 file which you then feed to powershell on the source box so that every process.popen looks exactly the same but the content of ps_buffer.ps1 changes with each execution.
powershell.exe ps_buffer.ps1
To keep your python code nice and clean, it's great having your list of powershell one liners in a json file or similar as well as pointers to scriptblocks you want to run saved into static files. You load up your json file as an ordered dict and cycle through issuing commands based on what you're doing.
Can't be overstated, as far as is possible try to be on the latest stable version of PS but more than that, it's imperative to be on the same version on client and server.
"scriptblock" and "server" are the values fed to this module or function
import subprocess
from string import Template
scriptblock = 'Get-ChildItem' #or a PS scriptblock as elaborate as you need
server = 'serverx'
psbufferfile = os.path.join(tempdir, 'pscmdbufferfile_{}.ps1'.format(server))
fullshellcmd = 'powershell.exe {}'.format(psbufferfile)
raw_pscommad = 'Invoke-Command -ComputerName $server -ScriptBlock {$scriptblock}'
pscmd_template = Template(raw_pscommand)
pscmd = pscmd_template.substitute(server=server, scriptblock=scriptblock)
try:
with open(psbufferfile, 'w') as psbf:
psbf.writelines(pscmd)
....
try:
process = subprocess.Popen(fullshellcmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, error = process.communicate()
....

Python, run terminal, and execute command in it

I am using Python 2.7.5, since this version is installed on the machine which I want to run script.
I have created a simple GUI in Tkinter, with button and text input.
Now in one input I provide the ip, or hostname of server, in next step I read the value of input fields and send it to linux bash terminal, and here I have a problem.
Reading the value from input field(works good)
nazwa_ip = self.Input_IP_hostname.get("1.0", 'end-1c')
and next:
os.system('gnome-terminal --window-with-profile=MY_PROFILE -e "ssh -t user_name#nazwa_ip"')
and here is the problem, because it wont change "nazwa_ip" to the read value. That comand send to terminal:
ssh -t user_name#nazwa_ip
but i want to send:
ssh -t user_name#ip_adres_from_input_field
Can somebody help me to resolve the issue?
according to the Python docs, it is recommended that os.system be replaced with the subprocess module .
status = os.system("mycmd" + " myarg")
# becomes
status = subprocess.call("mycmd" + " myarg", shell=True)
String formatting will work here:
os.system('gnome-terminal --window-with-profile=MY_PROFILE -e "ssh -t user_name#%s"' % nazwa_ip)
Using the subprocess method might be better to do this.
import subprocess
nazwa_ip = self.Input_IP_hostname.get("1.0", 'end-1c')
ssh_param = "ssh -t user_name#{}".format(nazwa_ip)
subprocess.call(['gnome-terminal', '--window-with-profile=MY_PROFILE', '-e', ssh_param])
Whilst running a subprocess is easy, starting one in a graphical terminal that behaves exactly like one the user launched is a little tricker. You could use my program interminal (link), which basically does what Stephen Rauch's answer does with gnome-terminal, but via a shell so that user environment variables and aliases etc are all available, which could be useful on the offchance that they affect how ssh runs.
You would use it from Python like this:
import subprocess
subprocess.Popen(['interminal', 'ssh', '-t', 'username#{}'.format(ip_address)])

Categories