History here: https://community.syncromsp.com/t/scripts-showing-as-successful-even-when-they-fail-because-script-execution-isnt-allowed/8206
In short: The current command that Syncro uses to run scripts leaves open the possibility of failure due to PowerShell execution policy or other issues preventing the script from running, while incorrectly marking the script run as successful. This could be fixed pretty easily by modifying the command.
— Overly-lengthy explanation of the problem: —
What Syncro does with scripts is package them as a PS1 file, download them to the asset, and then run a command to launch the script. Here’s that command (the name of the script is randomly-generated):
"C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe" -Sta -ExecutionPolicy Unrestricted -Command "& {C:\ProgramData\Syncro\bin\a575560c-0b80-42cb-b0bd-0349d86e110e.ps1; exit $LASTEXITCODE}"
- “C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe” - Launching PowerShell
- -Sta -ExecutionPolicy Unrestricted - Declaring STA mode and attempting to set execution policy to unrestricted.
- -Command "& {C:\ProgramData\Syncro\bin\a575560c-0b80-42cb-b0bd-0349d86e110e.ps1 - executing the downloaded script.
- ; exit $LASTEXITCODE}" - Taking the exit code from the script (if one was given) and exiting the PowerShell session with that same code.
The problem really comes in the last step. If the script declares an exit code the system variable $LASTEXITCODE is automatically set to that value, whatever it may be. That variable is then used as the exit code for the PowerShell session that Syncro first spun up. When that happens, the Syncro console picks up the code and uses it to determine and display in the web UI whether the script run was a Success or Failure.
If the script did not exit with a code, no code can be used to populate the system variable, and therefore the parent PowerShell session exits without a code. The Syncro console has to make an assumption of Failure or Success in this case, and they programmed it to presume Success. This is fine in my opinion. Presuming failure would force people to design scripts in ways they may normally not, and only fixes a small portion of the problem anyway.
The real issue happens either:
- When something prevents the script from running in the first place - execution policy doesn’t allow it, AV solution deletes the script before it can run, etc. - so no exit code can be passed to $LASTEXITCODE, even if the script was designed to do so. A script can’t exit if it never starts.
- When a script runs into an unhandled exception and terminates before passing an exit code.
— Potential solution: —
Add the following to the command after the PS1 file is launched and before the exit statement:
if(($? -eq $False) and !($LASTEXITCODE)){$LASTEXITCODE = 1}
$? is a boolean system variable which is True if the last command executed was successful and False if it was not. This applies to basically any command (including a command to display the variable itself), and any possible failure of that command. See this example screenshot from a system on which I’ve set execution policy to restricted, so scripts are not allowed to run:
Here’s another example. In this one, test.ps1 is a script that contains only a “throw” statement, and test2.ps1 is a script in which I intentionally left out a curly brace.
So $? will display False if either the script itself fails with an unhandled exception or if the command to run the script in the first place fails for whatever reason. We can use this in an inline IF statement before the exit by checking if $? equals “False” and - because we wouldn’t want to override an exit code passed by the script if there was one - checking for an exit code. If $? is False and $LASTEXITCODE is blank, we set $LASTEXITCODE to “1” so that it can then be passed to the exit statement. It would look like this:
"C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe" -Sta -ExecutionPolicy Unrestricted -Command "& {C:\ProgramData\Syncro\bin\a575560c-0b80-42cb-b0bd-0349d86e110e.ps1; if(($? -eq $False) and !($LASTEXITCODE)){$LASTEXITCODE = 1}; exit $LASTEXITCODE}"
Thus we can inform the Syncro console and web UI (and ourselves) of errors to which all parties currently remain blissfully unaware.