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: Python - Get command output cannot be decoded 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?