Python subprocess run or Popen running as specific user on Windows - python-3.x

Been working through this for a minute now, and figured I would ask here as I am sure it is something simple that I am missing.
Trying to silently run an installer using subprocess.run() (Have used subprocess.Popen, and os.system as well). I pass the full installer path, along with the arguments (As a list). Using all of the above, the installer runs successfully, and python waits for the installer to finish. I added a check to look at the PID of the installer process as just a backup check. However, running the script in full, I install some prerequisites (mainly dotnet 4.8) and then I restart. For cleanliness, I add the python script + arguments to the windows RunOnce registry key so it runs on next login. That works fine, but the installer never actually runs. It returns a 0 exit code, but never actually runs. Digging into it with event viewer, child processes (and maybe the parent process) is run by the NT System account. This causes the installer to silently fails since the System account doesn't have the permissions to run it. Looking for a way to run everything as a user to avoid the System account being used. Hence the question about running as an alternate user or the logged in user. (I don't know if this works on windows in all honesty)
Thoughts? Sample code snippets are below as well. Targeting Windows Server 2016+. Using python 3.11.2
Typical install code looks like the below. Installing it with subprocess works fine when run from command prompt manually. Its only when the RunOnce registry key triggers it does it silently fail. This leads me to believe its just the permissions/user error.
I also took a look at Python subprocess.Popen as different user on Windows but didn't have much luck implementing this. (Given it is 13 years old at this point)
# Installer path
installer = os.path.dirname(os.path.realpath(__file__)) + "\\setup.exe"
# Command for installing silently
# variables referenced here are all strings
ss_command = [
installer,
"-q",
"-s",
"InstallSecretServer=1",
"InstallPrivilegeManager=1",
'SecretServerUserDisplayName="Administrator"',
'SecretServerUserName="Administrator"',
"SecretServerUserPassword=" + '"' + administrator_password + '"',
"SecretServerAppUserName=" + '"' + service_account + '"',
"SecretServerAppPassword=" + '"' + service_account_password + '"',
"DatabaseIsUsingWindowsAuthentication=True",
"DatabaseServer=" + '"' + sql_hostname + '"',
"DatabaseName=" + '"' + database_name + '"',
"/l",
log_file
]
# Install attempt using Popen
installer_process = subprocess.Popen(ss_command)
# Install attempt using run
installer_process = subprocess.run(ss_command, capture_output=True, shell=False)
# Below are the functions for running executables and code from the registry.
# These work just fine, and arguments fed to the original script get passed to the registry key as well.
# Define a function to run an executable at boot
def run_at_startup_set(appname, arguments, path=None, user=False):
# Store the entry in the registry for running the application at startup
# Open the registry key path for applications that are run at login
key = win32api.RegOpenKeyEx(
win32con.HKEY_CURRENT_USER if user else win32con.HKEY_LOCAL_MACHINE,
Startup_Key_Path,
0,
win32con.KEY_WRITE | win32con.KEY_QUERY_VALUE
)
# Make sure the application is not already in the registry
i = 0
while True:
try:
name, _, _ = win32api.RegEnumValue(key, i)
except pywintypes.error as e:
if e.winerror == winerror.ERROR_NO_MORE_ITEMS:
break
else:
raise
if name == appname:
win32api.RegCloseKey(key)
return
i += 1
# Add arguments to the key
run_parameters = path or win32api.GetModuleFileName(0)
for arg in arguments:
run_parameters += ' "' + str(arg) + '"'
# Create a new key
win32api.RegSetValueEx(key, appname, 0, win32con.REG_SZ, run_parameters)
# Close the key
win32api.RegCloseKey(key)
return
# Define a function to run a script at boot
def run_script_at_startup_set(appname, arguments, user=False):
# Like run_at_startup_set(), but for source code files
run_at_startup_set(
appname,
arguments,
# Set the interpreter path (returned by GetModuleFileName())
# followed by the path of the current Python file (__file__).
'{} "{}"'.format(win32api.GetModuleFileName(0), __file__),
user
)
return

Related

Trying to run a command in bash terminal using python script

I'm trying to create a python script for the Automation of downloading python in a specific folder for a reason.
The only part of my code that for some reason doesn't work is
import os
path_python = os.path.expanduser("~/Desktop/file/Python-3.6.8")
_ = os.system("export PATH=" + path_python +":$PATH")
_ = os.system("export LD_LIBRARY_PATH=" + path_python + "/lib" + ":$LD_LIBRARY_PATH")
_ = os.system("which python")
if I type in bash terminal
export PATH=/home/user/Desktop/file/Python-3.6.8:$PATH
export LD_LIBRARY_PATH=/home/user/Desktop/file/Python-3.6.8/lib:$LD_LIBRARY_PATH
and check with which python it works fine but I want to use these commands from within python itself.
I want to be able to run this script on any computer so I can't specify the path unless there is a way to specify a general path to the desktop without using the user.

python subprocess call from wsgi

When I run the exact same function from the python3 interpreter vs, apache via mod wsgi, they both run error free but one returns the command text from apache the stout is simply always blank. Again I am running the exact same function.
Backgorund, I want to run svn update on some code to do so I am using subprocess to simply call "svn update /path/to/repo"
def update():
p1=subprocess.Popen(["svn", "update", "/var/www/myrepocode"],stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
(ret, stderr) = p1.communicate(timeout=10)
return (str(ret.decode('utf-8')))
when i run from the python3 shell or another python 3 script called from the shell
from test import update
print(update())
it works fine and i get
"Updating '/var/www/myrepocode':\nAt revision 27.\n"
when I have a wsgi script and execute it from accessing the web page
def application(environ, start_response):
...
output = output + "---"+update() +"---"
...
response_headers = [('Content-type', 'text/html'), ('Content-Length',
str(len(output)))]
start_response(status, response_headers)
return [output.encode('utf-8')]
I get an empty string:
------
I do not have a python virtual environment, just normal python3 and there are no errors thrown either. I suspect this has to do with the user running it but I really don't know. setting shell=True doesn't change anything either. Any help is greatly appreciated.
Your update method does not return nor print stderr . Maybe you should check for errors first by adding the following line in the update method.
print(stderr)
If errors are produced, can you post them?

Controlling a minecraft server with python

I've searched a lot for this and have not yet found a definitive solution. The closest thing I've found is this:
import shutil
from os.path import join
import os
import time
import sys
minecraft_dir = ('server diectory')
world_dir = ('server world driectory')
def server_command(cmd):
os.system('screen -S -X stuff "{}\015"'.format(cmd))
on = "1"
while True:
command=input()
command=command.lower()
if on == "1":
if command==("start"):
os.chdir(minecraft_dir)
os.system('"C:\Program Files\Java\jre1.8.0_111\bin\java.exe" -Xms4G -Xmx4G -jar craftbukkit-1.10.2.jar nogui java')
print("Server started.")
on = "0"
else:
server_command(command)
When I launch this program and type 'start' the CMD flashes up and closes instantly. Instead I want the CMD to stay open with the minecraft sever running from it. I'm not sure why this happens or what the problem is, any help would be greatly appreciated.
p.s. I have edited this to my needs (such as removing a backup script that was unnecessary) but it didn't work before. The original link is: https://github.com/tschuy/minecraft-server-control
os.system will simply run the command then return to your python script with no way to further communicate with it.
On the other hand using subprocess.Popen gives you access to the process while it runs, including writing to it's .stdin which is how you send data to the server:
def server_command(cmd):
process.stdin.write(cmd+"\n") #just write the command to the input stream
process = None
executable = '"C:\Program Files\Java\jre1.8.0_111\bin\java.exe" -Xms4G -Xmx4G -jar craftbukkit-1.10.2.jar nogui java'
while True:
command=input()
command=command.lower()
if process is not None:
if command==("start"):
os.chdir(minecraft_dir)
process = subprocess.Popen(executable, stdin=subprocess.PIPE)
print("Server started.")
else:
server_command(command)
you can also pass stdout=subprocess.PIPE so you can also read it's output and stderr=subprocess.PIPE to read from it's error stream (if any)
As well instead of process.stdin.write(cmd+"\n") you could also use the file optional parameter of the print function, so this:
print(cmd, file=process.stdin)
Will write the data to process.stdin formatted in the same way that print normally does, like ending with newline for you unless passing end= to override it etc.
Both of the above answers do not work in the environment I tried them in.
I think the best way is to use RCON, not sending keys to a window.
RCON is the protocol used by games to run commands.
Many python libraries support Minecraft RCON, and the default server.properties file has an option for RCON.
We will use the python module: MCRON.
Install it. It works for windows, mac, linux.
Type:
pip install mcrcon
Lets configure your server to allow RCON.
In server.properties, find the line 'enable-rcon' and make it look like this:
enable-rcon=true
Restart and stop your server.
Find the line 'rcon.password' and set it to any password you will remember.
You can leave the port default at 25575.
Now, open your terminal and type:
mcron localhost
Or your server ip.
You will be prompted to enter the password you set.
Then you can run commands and will get the result.
But we are doing this with python, not the PYPI MCRON scripts - so do this.
from mcrcon import MCRcon as r
with r('localhost', 'insertyourpasswordhere') as mcr:
resp = mcr.command('/list')
print(resp) #there are 0/20 players online: - This will be different for you.

How do I include this directory in the $PATH env var?

I'm building a package for Github's Atom editor and Im running into a challenge trying to get a child process to execute with node js. I'm pretty sure that the problem is that the environment that Atom runs in, doesn't include the path to the mrt script. So when I run this from within my package:
exec = require("child_process").exec
child = undefined
child = exec("/usr/local/bin/mrt add iron-router", { cwd: path },(error, stdout, stderr) -
console.log "stdout: " + stdout
console.log "stderr: " + stderr
console.log "exec error: " + error if error isnt null
return
)
in the console, I get:
Atom has a web inspector built right into it and you can actually see the Paths that atom has included. So when I go to Atom's console and type: process.env.PATH it shows the paths: /usr/bin:/bin:/usr/sbin:/sbin. So I somehow need to make atom aware of that mrt script's path. Anyone know how I might go about doing that?
I also reached out on on Atom's discussion forum yesterday, but have yet to come up with a solution.
Edit:
I should also note that the normal command for excuting the mrt package installer is mrt add package-name but as advised on Atom's discussion forum, I've been using the full path.
Edit 2:
I've creating symlinks to node in my /usr/bin directory, and it's working now. Now I'm trying to get node to create the symlinks for me using fs.symlink but that doesn't seem to be working.
To sum it up, the problem is that Atom uses PATH from where it is launched. Consequently, the path to node and the path to mrt where not included in Atom's path. The solution came to me when someone on the Atom Discussion forum pointed out Atom's Class BufferedNodeProcess.
At the time of Answer there is a slight bug with that class so I was not able to use it - the Github team works fast, I wouldn't be surprised if it was fixed within the next couple days. I was, however, able use some of the code to get Atom's environments. Also, I ended up using node's spawn method instead of execute since that's what BufferedNodeProcess uses. Plus you can read each individual line of the stdout.
options =
cwd: atom.project.getPath()
options.env = Object.create(process.env) unless options.env?
options.env["ATOM_SHELL_INTERNAL_RUN_AS_NODE"] = 1
node = (if process.platform is "darwin" then path.resolve(process.resourcesPath, "..", "Frameworks", "Atom Helper.app", "Contents", "MacOS", "Atom Helper") else process.execPath)
mrt = spawn(node, [
"/usr/local/lib/node_modules/meteorite/bin/mrt.js"
"add"
"iron-router"
], options )
mrt.stdout.on "data", (data) ->
console.log "stdout: " + data
return
mrt.stderr.on "data", (data) ->
console.log "stderr: " + data
return
mrt.on "close", (code) ->
console.log "child process exited with code " + code
return

pxssh error 'could not synchronize with original prompt'

I have been getting the below error while using pxssh to get into remote servers to run unix commands ( like uptime )
Traceback (most recent call last):
File "./ssh_pxssh.py", line 33, in
login_remote(hostname, username, password)
File "./ssh_pxssh.py", line 12, in login_remote
if not s.login(hostname, username, password):
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/site-packages/pexpect/pxssh.py", line 278, in login
**raise ExceptionPxssh ('could not synchronize with original prompt')
pexpect.pxssh.ExceptionPxssh: could not synchronize with original prompt**
Line 33 is where I call this function in main.
The function I am using is here :
def login_remote(hostname, username, password):
s = pxssh.pxssh()
s.force_password = True
if not s.login(hostname, username, password, auto_prompt_reset=False):
print("ssh to host :"+ host + " failed")
print(str(s))
else:
print("SSH to remote host " + hostname + " successfull")
s.sendline('uptime')
s.prompt()
print(s.before)
s.logout()
The error does not come each time I run the script. Rather it is intermittent. It comes 7 out of 10 times I run my script.
I have solved it by adding sync_multiplier argument to the login function.
s.login(hostname, username, password, sync_multiplier=5 auto_prompt_reset=False)
note that sync_multiplier is a communication timeout argument to perform successful synchronization. it tries to read prompt for at least sync_multiplier seconds.
Worst case performance for this method is sync_multiplier * 3 seconds.
I personally set sync_multiplier=2 but it depends on the communication speed on the system I work on.
I had the same problem when pxssh tried to login on a very slow connection.
The pexpect lib apparently was fooled by the remote motd prompt.
This remote motd prompt contained a uname -svr prompt, which itself contained a # character inside.
Apparently, pexpect saw it like a prompt. From that point, the lib was not in line anymore with the ssh session.
The following workaround was working for me:
just remove the # char inside /var/run/motd.dynamic (debian), or in /var/run/motd (ubuntu).
Another solution is to ask ssh to no prompt the motd while logging in . But this is not working for me:
i added the following:
PrintMotd no
in /etc/ssh/sshd_config
=> not working
Another workaround:
create a file in the home directory:
.hushlogin in the ~ directory

Resources