英文:
Catching the correct error code in Python and PowerShell
问题
It appears that you are encountering issues with exit codes not reflecting errors correctly in your Python FastAPI app when calling PowerShell scripts. To address this problem, you can try the following steps:
-
Check the Exit Code: Make sure that the PowerShell script itself is correctly setting the exit code when an error occurs. In your PowerShell script, you are using
throw $errorMessage
to throw an error. Ensure that this line is being executed when an error occurs and that it's not getting bypassed. -
PowerShell Execution Policy: Ensure that the PowerShell execution policy allows scripts to run. You can set it to "Unrestricted" or "RemoteSigned" depending on your security requirements.
-
Error Handling in Python: In your Python code, you can check the
result.returncode
immediately after running the PowerShell script and raise a 400 or 500 HTTP error based on the exit code. For example, ifresult.returncode != 0
, you can raise a 500 error indicating a script failure.
Here's a simplified example of how to handle this in Python:
import subprocess
from fastapi import HTTPException
result = subprocess.run(
# Your PowerShell command here,
shell=True,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
if result.returncode != 0:
raise HTTPException(
status_code=500,
detail=f"Script execution failed with error: {result.stderr}",
)
- Logging: Ensure that you are logging the PowerShell script's errors and output correctly to diagnose any issues further.
By following these steps, you should be able to better handle errors and return the correct HTTP status codes in your FastAPI app when calling PowerShell scripts.
英文:
I am running a Python FastAPI app with 4 different APIs. In the first one, I have implemented an error handling which after some trouble is working somehow fine. So I copied it's structure to the second API and suddenly it throws an error and won't work anymore.
All of my 4 APIs call a PowerShell script because it's easy to work with the Active Directory with PowerShell. After the first ps1 script is called, it calls another ps1 script under a priviledged user so that we get the permission to add groups to a specific OU (organizational unit in AD). So it's like this: Python app -> First ps1 -> 2nd ps1
My problem: Although there is an error at the 2nd script, I only get zero as an exit code / return code within the Python app and the 1st ps1 script.
I learnt that there is an issue within PowerShell encoding: https://stackoverflow.com/questions/67774165/python-get-command-output-cannot-be-decoded/67778646#67778646
So that's why I am trying to call the first ps script with utf-8 encoding. But I am failing still to trigger the 2nd PowerShell script with utf-8 as well.
To test the error handling, I purposefully implemented a division by zero.
Within 2nd script:
Start-Transcript -Append .\managead\ADMgmtlog.txt
Try {
#Add-ADGroupMember -Identity $groupname -Members $techuser;
Write-Host "PS1 script ADgroupmgmt: let's crash the script "
$result = 10/0
} catch {
$errorMessage = $_.Exception.Message
Write-Host "PS1 script ADgroupmgmt: Error: " + $errorMessage
Write-Host "PS1 script ADgroupmgmt: The AD Management script has failed"
$ErrorActionPreference = 'Stop'
Write-Host "PS1 script ADgroupmgmt: before throw "
throw $errorMessage
Write-Host "PS1 script ADgroupmgmt: after throw"
Stop-Transcript
}
Then, I within the first ps script, I call this 2nd one and try to catch the error:
try {
#$enc = [System.Text.Encoding]::UTF8 #not used currently
$process = Start-Process -NoNewWindow pwsh.exe -Credential $credential -ArgumentList $ArgumentList #-PassThru
#do{sleep 1}while(Get-Process -Id $process.Id -Ea SilentlyContinue)
Write-Host "PS1 script ADMgmtInitial: process: " + $process
#Write-Host "PS1 script ADMgmtInitial: Now lets decode with GetBytes"
#$encprocess= $enc.GetBytes($process)
#Write-Host "PS1 script ADMgmtInitial: encprocess: " + $encprocess
#$exitCode = $encprocess.ExitCode
$exitCode = $process.ExitCode
Write-Host "PS1 script ADMgmtInitial: exitCode: " + $exitCode
Write-Host "PS1 script ADMgmtInitial: ps1 Request success | action = ${action}; groupname = ${groupname}; tech user = ${techuser}; region = ${region}"
} catch {
<#
$encprocess= $enc.GetBytes($process)
Write-Host "PS1 script ADMgmtInitial: encprocess: " + $encprocess
$exitCode = $encprocess.ExitCode
#>
$exitCode = $process.ExitCode
Write-Host "PS1 script ADMgmtInitial: exitCode: " + $exitCode
$errorMessage = $_.Exception.Message
Write-Host "PS1 script ADMgmtInitial: Error: " + $errorMessage
Write-Host "PS1 script ADMgmtInitial: The AD Management script has failed"
throw $errorMessage
$ErrorActionPreference = 'Stop'
}
By throwing another error within this catch command, the errorMessage should be passed to the Python app. Here, I am triggering the first ps script like this:
result = subprocess.run(
f'''\
chcp 65001 >NUL & pwsh.exe -File "./managead/ADMgmtInitial.ps1" -action "{item.action}" -groupname "{providedadgroup}" -techuser "{item.techuser}" -region "{item.region}"
''',
shell=True, # call via cmd.exe (on Windows)
text=True, # decode output as text
stdout=subprocess.PIPE, # capture stdout instead of printing to the console
stderr=subprocess.PIPE # capture stderr
)
# Print stdout and stderr output for diagnostic purposes.
print("stdout: " + result.stdout)
print("stderr: " + result.stderr)
returncodestring = str(result.returncode)
print ("returncode in Python: " + returncodestring)
print("AD group changed | Action= " + item.action + "; csname = " + item.csname + "; Tech User = " + item.techuser + "; Region = " + item.region)
#check error code
if result.returncode == 0: #0 stands for no returned error; everything was executed without errors
stdoutmessage = result.stderr
print ("stdoutmessage: ")
print (stdoutmessage)
#return specific messages depended on the action type add or remove
if item.action == "add":
print("User " + item.techuser + " has been added to the AD group " + providedadgroup)
return {"message": "Success: User " + item.techuser + " has been added to the AD group " + providedadgroup}
else: #action == "remove"
print("User " + item.techuser + " has been removed from the AD group " + providedadgroup)
return {"message": "Success: User " + item.techuser + " has been removed from the AD group " + providedadgroup}
else: # !=0 stands for an error; something went wrong
returncodestring = str(result.returncode)
print ("returncode in Python: " + returncodestring)
errormessagestring= result.stderr.strip()
print ("Python Error | errormessagestring: " + errormessagestring)
#return specific messages depended on the action type add or remove
if item.action == "add":
print("Error: User " + item.techuser + " could not be added to the AD group " + providedadgroup)
raise HTTPException(
status_code=500,
detail="Failed to add user " + item.techuser + " to the AD group " + providedadgroup +". Details: " + errormessagestring,
headers={"Error": "Could not add user " + item.techuser + " to the AD group " + providedadgroup},
)
else: #action == "remove"
print("Error: User " + item.techuser + " could not be removed from the AD group " + providedadgroup)
raise HTTPException(
status_code=500,
detail="Failed to remove user " + item.techuser + " from the AD group " + providedadgroup +". Details: " + errormessagestring,
headers={"Failed to remove user " + item.techuser + " from the AD group " + providedadgroup},
)
Now, although there is an error thrown, I get exit code == 0 within the first ps script. This code is passed to the Python app where the result.returncode is zero as well. It's really weird because the error is actually regonized when I print it. This is the part of my console:
Action successful: E415_myOU_ as a prefix has been added. Result: E415_myOU_mygroup
stdout: Transcript started, output file is .\managead\ADInitiallog.txt
PS1 script ADMgmtInitial: ps1 Request received | action = add; groupname = E415_myOU_mygroup; techuser = myuserid; region = EMEA
PS1 script ADMgmtInitial: ArgumentList :-noprofile -file ".\managead\ADgroupmgmt.ps1" -action add -groupname E415_myOU_mygroup -techuser myuserid -region EMEA
PS1 script ADMgmtInitial: Execution started for region EMEA
PS1 script ADMgmtInitial: Calling the ADgroupmgmt.ps1 script to add myuserid to the group E415_myOU_mygroup
PS1 script ADMgmtInitial: AD Group E415_myOU_mygroup is available: CN=E415_myOU_mygroup,OU=Groups,OU=myOU,OU=notshown,DC=emea,DC=notshown,DC=net
PS1 script ADMgmtInitial: techuser is a user. Trying to find the user.
PS1 script ADMgmtInitial: AD user myuserid is available: CN=myname\, myname(123),OU=Users,OU=_GlobalResources,OU=notshown,DC=emea,DC=notshown,DC=net
PS1 script ADMgmtInitial: process: +
PS1 script ADMgmtInitial: exitCode: +
PS1 script ADMgmtInitial: ps1 Request success | action = add; groupname = E415_myOU_mygroup; tech user = myuserid; region = EMEA
Transcript stopped, output file is .\managead\ADInitiallog.txt
Transcript started, output file is .\managead\ADMgmtlog.txt
PS1 script ADgroupmgmt: ps1 Request received | action = add; groupname = E415_myOU_mygroup; techuser = myuserid; region = EMEA
PS1 script ADgroupmgmt: ps1 Request | Starting to add myuserid to E415_myOU_mygroup
PS1 script ADgroupmgmt: let's crash the script
PS1 script ADgroupmgmt: Error: + Attempted to divide by zero.
PS1 script ADgroupmgmt: The AD Management script has failed
PS1 script ADgroupmgmt: before throw
stderr: The specified drive root "C:\Users\myuserid\AppData\Local\Temp\" either does not exist, or it is not a folder. #comment added afterwards: no idea why this error shows up
Exception: .\managead\ADgroupmgmt.ps1:41:7
Line |
41 | throw $errorMessage
| ~~~~~~~~~~~~~~~~~~~
| Attempted to divide by zero.
returncode in Python: 0
AD group changed | Action= add; csname = sourcename; Tech User = myuserid; Region = EMEA
stdoutmessage:
The specified drive root "C:\Users\myuserid\AppData\Local\Temp\" either does not exist, or it is not a folder.
Exception: .\managead\ADgroupmgmt.ps1:41:7
Line |
41 | throw $errorMessage
| ~~~~~~~~~~~~~~~~~~~
| Attempted to divide by zero.
User myuserid has been added to the AD group E415_myOU_mygroup #should not be shown because if in-built error
INFO: 127.0.0.1:55917 - "POST /managead HTTP/1.1" 200 OK
So, the API should never return http code 200! I need to prevent this and return the correct 400 or 500 http code.
Do you have any idea why I am not getting the correct exit codes?
答案1
得分: 2
你无法在Python中捕获PowerShell异常,因此不需要在catch块中再次抛出异常。相反,将错误消息写入错误流并使用所需的退出代码退出PowerShell。示例:
Write-Error $errorMessage -ErrorAction Continue
exit $exitCode
在Python中,您可以从stderr
读取错误消息,返回代码将等于PowerShell中的$exitCode
。
如果您想要在PowerShell中的另一个PowerShell脚本中捕获PowerShell异常,您必须在相同的上下文中执行它,可以通过直接调用它或使用点运算符引用它来实现。由于您使用嵌套的PowerShell调用来以提升的权限运行它,因此无法直接调用第二个脚本。您将不得不坚持使用Start-Process
的方法。但您可以应用与第一个脚本相同的逻辑:
使用exit
传播您的退出代码。要获取stdout
/stderr
,请参阅这个问答。
英文:
You will not be able to catch a PowerShell exception in Python. So there is no need to throw in your catch block again. Instead, write your error message to the error stream and exit PowerShell with your desired exit code. Example:
Write-Error $errorMessage -ErrorAction Continue
exit $exitCode
In Python, you can then read your error message from stderr
and the return code will equal your $exitCode
from PowerShell.
If you want to catch a PowerShell exception from another PowerShell script in PowerShell, you have to execute it in the same context by directly calling it or by dot sourcing it. As you use the nested PowerShell invocation to run it elevated, you cannot call your second script directly. You will have to stick to your approach with Start-Process
. But you can apply the same logic as you do in your first script:
Propagate your exit code with exit
. To get stdout
/stderr
, have a look at this Q&A.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论