I want to write a powershell script which activates a conda environment and runs some python commands afterwards. The script should log its progress and possible errors to a file.
When trying to activate a conda environment which doesn't exists, the following problem arises:
C:\Path\to\conda.exe shell.powershell hook | Out-String | Invoke-Expression
Invoke-Expression "conda activate ABC"
results in the output:
EnvironmentNameNotFound: Could not find conda environment: ABC
You can list all discoverable environments with `conda info --envs`.
Invoke-Expression: Cannot bind argument to parameter 'Command' because it is an empty string
The first part is what I want to log, the second part is an error which is thrown because the result of the command is empty. I dont want to log this part.
Currently my script looks like this (I replaced my logging function by Write-Output):
C:\Path\to\conda.exe shell.powershell hook | Out-String | Invoke-Expression
Write-Output "Activating environment: ABC"
try {
$envActivateOutput = Invoke-Expression "conda activate ABC" *>&1
} catch [System.Management.Automation.ParameterBindingException]{
Write-Output "Error: $envActivateOutput" # this is empty
throw
} catch {
Write-Output "Error: $_"
throw
}
How can I catch the result of conda activate ABC
in $envActivateOutput
and log it, while not logging the ParameterBindingException?
, but possible other Exceptions?
The general advice applies:
Invoke-Expression
(iex
) should generally be avoided and used only as a last resort, due to its inherent security risks. Superior alternatives are usually available. If there truly is no alternative, only ever use it on input you either provided yourself or implicitly trust - see this answer.Also, generally speaking, a try { ... } catch { ... } finally { ... }
statement only acts on terminating errors, which stderr output from external programs such as conda.exe
does not generally trigger.
While conda.exe shell.powershell hook | Out-String | Invoke-Expression
is a legitimate use of Invoke-Expression
, Invoke-Expression "conda activate ABC"
is not.
Just invoke conda activate ABC
directly, in which case applying redirection *>&1
does work (ditto for a variable-based argument: conda activate $task.python_env
)
Invoke-Expression
behavior you ran into is a bona fide bug: see GitHub issue #10476 and this answer.To filter out the error message Invoke-Expression: Cannot bind argument to parameter 'Command'...
from the captured output, you can filter the captured output by weeding out the object that has a .FullyQualifiedErrorId
property value of 'ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.PowerShell.Commands.InvokeExpressionCommand'
, which is only true for instances of the System.Management.Automation.ErrorRecord
type underlying said error message.
Therefore:
C:\Path\to\conda.exe shell.powershell hook | Out-String | Invoke-Expression
"Activating environment: ABC"
$envActivateOutput =
conda activate ABC *>&1 |
Where-Object FullyQualifiedErrorId -ne 'ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.PowerShell.Commands.InvokeExpressionCommand' |
ForEach-Object {
# Fix for a Windows PowerShell bug (not needed in PowerShell 7):
# *Empty* stderr lines stringify to verbatim 'System.Management.Automation.RemoteException'
if ("$_" -eq 'System.Management.Automation.RemoteException') { '' }
else { "$_" } # stringify the [ErrorRecord]-wrapped stderr line.
}
Note:
You could alternatively do
Where-Object Exception -isnot [System.Management.Automation.ParameterBindingException]
, but this test is less specific than the one above.
The ForEach-Object
call at the end ensures that the captured output is properly stringified instead of creating "noisy" error-like output.
This is needed, because when you merge stderr output from an external program (which is what the text before the Invoke-Expression
-related error in your output represents) into the success output stream (via 2>&1
or *>&1
), PowerShell wraps each line in a [System.Management.Automation.ErrorRecord]
instance.
As noted in the source-code comments, additional work is needed in Windows PowerShell to work around a bug that causes the error-record wrappers around empty stderr lines to mistakenly stringify as verbatim System.Management.Automation.RemoteException
rather than as the empty string.
Also note:
The conda
in conda activate ABC *>&1
is not conda.exe
at that point, but an alias that refers to the Invoke-Conda
command from the Conda PowerShell module, due to the initialization performed via the initial conda.exe
call.
The "Invoke-Expression: Cannot bind argument to parameter 'Command' ..." error is being relayed by Invoke-Conda
as a non-terminating error, even though it is itself (statement-)terminating. It is the surfacing as a non-terminating error that allows it to be captured via output stream redirections such as *>&1
.