Public/Invoke-Python.ps1
|
Function Invoke-Python { <# .SYNOPSIS Run a Python Script .DESCRIPTION This function executes a python script and returns output in a PowerShell friendly way .PARAMETER Script A ScriptBlock-equivelent. .PARAMETER ScriptPath File Path of a python script file to execute. .EXAMPLE Invoke-Python -Script 'print("Hello, World!")' .EXAMPLE Invoke-Python -Path 'perform_action.py' .EXAMPLE $PythonOutput = Invoke-Python -Path 'perform_action.py' -Passthru .EXAMPLE Invoke-Python -Path 'perform_action.py' -Console ## Writes output to console using Write-Host .OUTPUTS Array of Strings output by the script, only when using -Passthru #> param( [CmdletBinding()] [Parameter(mandatory = $true, ParameterSetName = 'File')]$ScriptPath, [Parameter(mandatory = $true, ParameterSetName = 'Script')][string]$Script, [Parameter(mandatory = $false)][String]$PythonPath = 'C:\Python313\python.exe', [Parameter(mandatory = $false)][String]$PythonVersion, [Parameter(mandatory = $false)][Switch]$PassThru, [Parameter(mandatory = $false)][Switch]$Console ) begin { if (-Not $PythonPath) { if ($IsMacOS) { $PythonPath = "/usr/local/bin/python$($PythonVersion)" } } ## Test if PsExec is installed $PythonFileExists = Test-Path -Path $PythonPath if (-Not $PythonFileExists) { Throw "Python is not installed at $($PythonPath). Install there, or use the -PythonPath Parameter to define the copy to use." } switch ($PSCmdlet.ParameterSetName) { 'Script' { $ScriptPath = "$(New-TemporaryFile).py" Set-Content -Path $ScriptPath -Value $Script.ToString() -Force break } 'File' { ## Test if PsExec is installed if (-not (Test-Path -Path $ScriptPath)) { Throw "The file does not exist: $($ScriptPath)" } break } } } process { try { $PythonArguments = "-q" # don't print version and copyright messages on interactive startup $PythonArguments += " -u " # force the stdout and stderr streams to be unbuffered; $PythonArguments += $ScriptPath ## Setup Standard Process Options $PythonProcInfo = New-Object System.Diagnostics.ProcessStartInfo $PythonProcInfo.FileName = $PythonPath $PythonProcInfo.UseShellExecute = $false $PythonProcInfo.CreateNoWindow = $true ## Using Normal actually hides the window ## Because the CreateNoWindow is True $PythonProcInfo.WindowStyle = 'Hidden' ## Redirect Standard Streams $PythonProcInfo.RedirectStandardError = $true ## True $PythonProcInfo.RedirectStandardOutput = $true ## True $PythonProcInfo.RedirectStandardInput = $true ## True $PythonProcInfo.Arguments = $PythonArguments $PythonProc = New-Object System.Diagnostics.Process $PythonProc.StartInfo = $PythonProcInfo ## Create an Event Handler for Standard Out $global:PythonStdOut = [System.Text.StringBuilder]::New() $global:PythonStdErr = [System.Text.StringBuilder]::New() ## Start the Python Process [Void]$PythonProc.Start() ## Keep reading Standard Out until the stream has ended while (!($PythonProc.StandardOutput.EndOfStream)) { $Line = $PythonProc.StandardOutput.ReadLine() [void]($global:PythonStdOut.AppendLine($Line)) if ($Console) { Write-Host $Line } } ## Then read all of Standard Err while (!($PythonProc.StandardError.EndOfStream)) { $Line = $PythonProc.StandardError.ReadLine() [void]($global:PythonStdErr.AppendLine($Line)) Write-Host $Line } ## Wait for the Process to Exit $PythonProc.WaitForExit() } catch { throw $_ } finally { $PythonProc.Close() $PythonProc.Dispose() if ($PSCmdlet.ParameterSetName -eq 'Script') { Remove-Item -Path $ScriptPath -Force } } ## Convert the output to a String $PythonOut = $global:PythonStdOut.toString().Trim() $PythonErr = $global:PythonStdErr.toString().Trim() ## Throw on Fatal Errors if ($PythonErr) { throw $PythonErr } } end { if ($PassThru) { return $PythonOut } } } |