45

I have a need to run a PowerShell function from a Python script. Both the .ps1 and the .py files currently live in the same directory. The functions I want to call are in the PowerShell script. Most answers I've seen are for running entire PowerShell scripts from Python. In this case, I'm trying to run an individual function within a PowerShell script from a Python script.

Here is the sample PowerShell script:

# sample PowerShell
Function hello
{
    Write-Host "Hi from the hello function : )"
}

Function bye
{
    Write-Host "Goodbye"
}

Write-Host "PowerShell sample says hello."

and the Python script:

import argparse
import subprocess as sp

parser = argparse.ArgumentParser(description='Sample call to PowerShell function from Python')
parser.add_argument('--functionToCall', metavar='-f', default='hello', help='Specify function to run')

args = parser.parse_args()

psResult = sp.Popen([r'C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe',
'-ExecutionPolicy',
'Unrestricted',
'. ./samplePowerShell',
args.functionToCall],
stdout = sp.PIPE,
stderr = sp.PIPE)

output, error = psResult.communicate()
rc = psResult.returncode

print "Return code given to Python script is: " + str(rc)
print "\n\nstdout:\n\n" + str(output)
print "\n\nstderr: " + str(error)

So, somehow, I want to run the 'hello()' or the 'bye()' function that is in the PowerShell sample. It would also be nice to know how to pass in parameters to the function. Thanks!

H_H
  • 605
  • 1
  • 9
  • 13

3 Answers3

58

You want two things: dot source the script (which is (as far as I know) similar to python's import), and subprocess.call.

import subprocess
subprocess.call(["C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\powershell.exe", ". \"./SamplePowershell\";", "&hello"])

so what happens here is that we start up powershell, tell it to import your script, and use a semicolon to end that statement. Then we can execute more commands, namely, hello.

You also want to add parameters to the functions, so let's use the one from the article above (modified slightly):

Function addOne($intIN)
{
    Write-Host ($intIN + 1)
}

and then call the function with whatever parameter you want, as long as powershell can handle that input. So we'll modify the above python to:

import subprocess
subprocess.call(["C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\powershell.exe", ". \"./SamplePowershell\";", "&addOne(10)"])

this gives me the output:

PowerShell sample says hello.
11
Adam R. Grey
  • 1,861
  • 17
  • 30
  • 1
    Thank you. That is quite helpful! I seem to be successful using the subprocess.Popen() call as well as the subprocess.call(), now after having included the escape sequences you pointed out. May I ask if there's an advantage to using subprocess.call() over subprocess.Popen()? My first reason for using Popen() is to gather the stdout, stderr, and the return code. Can I do that with subprocess.call() as well? Also thank you for the syntax to calling functions with parameters. – H_H Jan 28 '13 at 22:03
  • 1
    subprocess.call() waits until the program is done. Popen is more useful if you want to be able to communicate with the program while it's running. If you want the stdout and stderr streams, the only way I know to get those with the subprocess module is to output them to text files, like so: returnValue= subprocess.call("", executable="fposo.exe", stdout=open("out.txt", "w+"), stderr=open("err.txt", "w+")) print "childProcess returned " + str(returnValue) – Adam R. Grey Jan 29 '13 at 02:50
  • 1
    more relevantly: print "childProcess returned " + str(subprocess.call([". \"./SamplePowershell\";", "&hello"], executable="C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\powershell.exe", stdout=open("out.txt", "w+"), stderr=open("err.txt", "w+"))) – Adam R. Grey Jan 29 '13 at 02:57
  • 1
    As an aside: In PowerShell, functions are invoked _like shell commands_ - `foo arg1 arg2` - _not_ like C# methods - `foo(arg1, arg2)`; see [`Get-Help about_Parsing`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing). If you use `,` to separate arguments, you'll construct an _array_ that a function sees as a _single_ argument. To prevent accidental use of method syntax, use [`Set-StrictMode -Version 2`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/set-strictmode) or higher, but note its other effects. – mklement0 Sep 12 '20 at 12:13
  • 1
    As an aside: I suggest not using `Write-Host` in sample code, as it might mislead beginners to think that (a) using a cmdlet is necessary to produce output in PowerShell (it isn't) and that (b) the cmdlet to use for producing output is `Write-Host` (it generally isn't, unless your intent is to _write directly to the screen_ (host) rather than to _output data_). – mklement0 Sep 12 '20 at 12:13
  • As an aside: Passing a command string to PowerShell's [CLI](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pwsh) defaults to binding to the `-Command` / `-c` parameter in _Windows PowerShell_ (`powershell.exe`), which is appropriate here. _PowerShell [Core] v6+_ (`pwsh`) now defaults to `-File`, however, so to adapt the code to v6+ would require explicit use of `-c` preceding the string. – mklement0 Sep 12 '20 at 12:20
3

Here is a short and simple way to do that

import os
os.system("powershell.exe echo hello world")
0

Tested on Python 3.10 on latest Windows 10 x64 as of 2023-06-05:

# Auto-detect location of powershell ...
process = subprocess.Popen("where powershell", stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
stdout, stderr = stdout.decode('utf-8'), stderr.decode('utf-8')
powershell_path = stdout.strip()

# ... then run Powershell command and display output.
process = subprocess.Popen(f"{powershell_path} echo 'Hello world!'", stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
stdout, stderr = stdout.decode('utf-8'), stderr.decode('utf-8')
print(stdout)

Output:

Hello world!

The advantage of this script is that it auto-detects the location of powershell, which avoids issues as it varies between Windows versions.

Contango
  • 76,540
  • 58
  • 260
  • 305