Search code examples
powershellconsoleazure-pipelinespowershell-ise

Exception setting "TreatControlCAsInput": "The handle is invalid. in Windows PowerShell ISE


We have a PowerShell script that contains "[Console]::TreatControlCAsInput = $true". The script is executed in a Azure DevOps Pipeline and fails with

Exception setting "TreatControlCAsInput": "The handle is invalid.
"
At line:1 char:3
+   [Console]::TreatControlCAsInput = $true
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenSetting

We've narrowed down the issue by reproducing this with a single line of code outside of an Azure Pipeline. Simply open Windows PowerShell ISE and run this:

[Console]::TreatControlCAsInput = $true

If we do this in a Windows PowerShell command window, the line of code succeeds.

Does anyone know what is different about the two environments that causes this difference in behavior? We are hoping that the answer would help us understand and resolve the larger issue with the script that runs in an Azure Pipeline.

The script runs fine if executed:

  1. In Cmd.exe window using Powershell.exe -File ....
  2. In Windows PowerShell window

The script fails if executed in WIndows PowerShell ISE


Solution

  • Note:

    • This answer focuses on the behavior of the ISE.

    • Azure DevOps pipelines apparently exhibit similar behavior with respect to non-support of interactive console features, but - as Mathias R. Jessen points out - such features are pointless in an environment where only unattended execution ever takes place, i.e. where user interaction is fundamentally unsupported.

    • The upshot is:

      • Avoid the ISE if you need a full, interactive console experience, and consider migrating to Visual Studio Code (see bottom section).

      • Scripts run in Azure DevOps pipeline must avoid use of interactive features altogether.


    The Windows PowerShell ISE does not allocate a console by default, which causes attempts to call the members of the static .NET [Console] class to throw the exception you saw.

    While it allocates a (hidden) console (used behind the scenes) on demand - namely the first time you run an external console application (e.g. cmd /c ver) - after which [Console] members then can be accessed, any interactive console functionality is fundamentally unsupported in the ISE.

    Since [Console]::TreatControlCAsInput = $true is only relevant to interactive functionality, notably [Console]::ReadKey():

    • There is no point in setting it in the ISE, and you can simply ignore the failure:

       try { [Console]::TreatControlCAsInput = $true } catch {}
      
    • However, if your script relies on the ability to interpret Ctrl+C as a keypress rather than a request to abort execution, you may need to find an ISE-compatible solution.

      • Note that Read-Host and $host.UI.ReadLine() do not allow configuring the behavior of Ctrl+C (they always abort), and that $host.UI.RawUI.ReadKey(), which does, is not implemented in the ISE.

    Note: