PowerShell / PowerShellEditorServices

A common platform for PowerShell development support in any editor or application!
MIT License
622 stars 213 forks source link

VS Code Powershell Editor Services Throws Exception If Powershell Profile Contains ErrorActionPreference=Stop #1976

Closed caparkaya closed 1 year ago

caparkaya commented 1 year ago

Prerequisites

Steps to reproduce

Hello,

With the latest update of VS Code (v1.74.1) Powershell Extension (v2022.12.1), with Terminal > Integrated > Shell Integration: Enabled (Checked), Powershell Editor Services throws exception at startup if the powershell profile script file (Microsoft.VSCode_profile.ps1) contains

$ErrorActionPreference = "Stop"; Set-StrictMode -Version Latest

It fails in file "src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs", at line 567 (function Global:Prompt()):

$Global:__LastHistoryId = $LastHistoryEntry.Id

because $LastHistoryEntry is null. Looking at the code fragment, I understand that at line 529 the assignment statement might result in $null value (or the resulting object might not contain Id property) which is not handled properly.

Expected behavior

Setting

$ErrorActionPreference = "Stop"; Set-StrictMode -Version Latest

in powershell profile script should not cause any startup errors inside Powershell Editor Services.

Actual behavior

At editor services startup (with Powershell profile loaded in terminal):

The property 'Id' cannot be found on this object. Verify that the property exists.
At line:57 char:2
+     $Global:__LastHistoryId = $LastHistoryEntry.Id
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : PropertyNotFoundStrict

Error details

[Warn  - 12:01:11] Microsoft.PowerShell.EditorServices.Services.PowerShell.Host.PsesInternalHost: Runtime exception occurred while executing command:

System.Management.Automation.RuntimeException: The variable '$IsWindows' cannot be retrieved because it has not been set.
   at System.Management.Automation.Runspaces.PipelineBase.Invoke(IEnumerable input)
   at System.Management.Automation.PowerShell.Worker.ConstructPipelineAndDoWork(Runspace rs, Boolean performSyncInvoke)
   at System.Management.Automation.PowerShell.Worker.CreateRunspaceIfNeededAndDoWork(Runspace rsToUse, Boolean isSync)
   at System.Management.Automation.PowerShell.CoreInvokeHelper[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)
   at System.Management.Automation.PowerShell.CoreInvoke[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)
   at System.Management.Automation.PowerShell.Invoke[T](IEnumerable input, PSInvocationSettings settings)
   at Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility.PowerShellExtensions.InvokeAndClear[TResult](PowerShell pwsh, PSInvocationSettings invocationSettings)
   at Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution.SynchronousPowerShellTask`1.ExecuteNormally(CancellationToken cancellationToken) | 
[Error - 12:01:11] OmniSharp.Extensions.JsonRpc.DefaultRequestInvoker: Failed to handle request initialize 0 - System.Management.Automation.RuntimeException: The variable '$IsWindows' cannot be retrieved because it has not been set.
   at System.Management.Automation.Runspaces.PipelineBase.Invoke(IEnumerable input)
   at System.Management.Automation.PowerShell.Worker.ConstructPipelineAndDoWork(Runspace rs, Boolean performSyncInvoke)
   at System.Management.Automation.PowerShell.Worker.CreateRunspaceIfNeededAndDoWork(Runspace rsToUse, Boolean isSync)
   at System.Management.Automation.PowerShell.CoreInvokeHelper[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)
   at System.Management.Automation.PowerShell.CoreInvoke[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)
   at System.Management.Automation.PowerShell.Invoke[T](IEnumerable input, PSInvocationSettings settings)
   at Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility.PowerShellExtensions.InvokeAndClear[TResult](PowerShell pwsh, PSInvocationSettings invocationSettings)
   at Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution.SynchronousPowerShellTask`1.ExecuteNormally(CancellationToken cancellationToken)
   at Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution.SynchronousPowerShellTask`1.Run(CancellationToken cancellationToken)
   at Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution.SynchronousTask`1.ExecuteSynchronously(CancellationToken executorCancellationToken)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.PowerShell.EditorServices.Services.PowerShell.Host.PsesInternalHost.<TryStartAsync>d__81.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at OmniSharp.Extensions.LanguageServer.Server.LanguageServer.<MediatR-IRequestHandler<OmniSharp-Extensions-LanguageServer-Protocol-Models-InternalInitializeParams\,OmniSharp-Extensions-LanguageServer-Protocol-Models-InitializeResult>-Handle>d__78.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at OmniSharp.Extensions.LanguageServer.Server.Pipelines.SemanticTokensDeltaPipeline`2.<Handle>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at OmniSharp.Extensions.LanguageServer.Server.Pipelines.ResolveCommandPipeline`2.<Handle>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MediatR.Pipeline.RequestPreProcessorBehavior`2.<Handle>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MediatR.Pipeline.RequestPostProcessorBehavior`2.<Handle>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MediatR.Pipeline.RequestExceptionProcessorBehavior`2.<Handle>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at MediatR.Pipeline.RequestExceptionProcessorBehavior`2.<Handle>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MediatR.Pipeline.RequestExceptionActionProcessorBehavior`2.<Handle>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at MediatR.Pipeline.RequestExceptionActionProcessorBehavior`2.<Handle>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at OmniSharp.Extensions.JsonRpc.RequestRouterBase`1.<<RouteRequest>g__InnerRoute|7_0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at OmniSharp.Extensions.JsonRpc.RequestRouterBase`1.<RouteRequest>d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at OmniSharp.Extensions.JsonRpc.DefaultRequestInvoker.<>c__DisplayClass10_0.<<RouteRequest>b__5>d.MoveNext() | Method='initialize' RequestId='0'
[Warn  - 12:01:11] Microsoft.PowerShell.EditorServices.Services.PowerShell.Host.PsesInternalHost: Runtime exception occurred while executing command:

System.Management.Automation.PropertyNotFoundException: The property 'Id' cannot be found on this object. Verify that the property exists.
   at System.Management.Automation.Runspaces.PipelineBase.Invoke(IEnumerable input)
   at System.Management.Automation.PowerShell.Worker.ConstructPipelineAndDoWork(Runspace rs, Boolean performSyncInvoke)
   at System.Management.Automation.PowerShell.Worker.CreateRunspaceIfNeededAndDoWork(Runspace rsToUse, Boolean isSync)
   at System.Management.Automation.PowerShell.CoreInvokeHelper[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)
   at System.Management.Automation.PowerShell.CoreInvoke[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)
   at System.Management.Automation.PowerShell.Invoke[T](IEnumerable input, PSInvocationSettings settings)
   at Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility.PowerShellExtensions.InvokeAndClear[TResult](PowerShell pwsh, PSInvocationSettings invocationSettings)
   at Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution.SynchronousPowerShellTask`1.ExecuteNormally(CancellationToken cancellationToken) | 
[Warn  - 12:01:11] OmniSharp.Extensions.LanguageServer.Server.LspServerOutputFilter: Tried to send request or notification before initialization was completed and will be sent later OmniSharp.Extensions.JsonRpc.Server.Messages.InternalError | @Request='OmniSharp.Extensions.JsonRpc.Server.Messages.InternalError'

Environment data

Name                           Value
----                           -----
PSVersion                      5.1.19041.1682
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.1682
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Version

Powershell Extension Version: v2022.12.1, VSCode Version: 1.74.1

Visuals

No response

ninmonkey commented 1 year ago

At the end of the first pass, It looks like it sets the global to null

 $Global:__LastHistoryId = $LastHistoryEntry.Id # which is null

pass 1:

pass 2:

pass 3

My annotations are the 🦓-comments. The rest is the truncated original.

# Prevent installing more than once per session
if (Test-Path variable:global:__VSCodeOriginalPrompt) {
    return;
}
# Disable shell integration when the language mode is restricted
if ($ExecutionContext.SessionState.LanguageMode -ne "FullLanguage") {
    return;
}
$Global:__VSCodeOriginalPrompt = $function:Prompt
$Global:__LastHistoryId = -1
function Global:Prompt() {
    $FakeCode = [int]!$global:?
    $LastHistoryEntry = Get-History -Count 1 
    # 🦓 $LastHistoryEntry: is true null

    # Skip finishing the command if the first command has not yet started
    if ($Global:__LastHistoryId -ne -1) {
        # 🦓 -1 -ne -1 == false

        if ($LastHistoryEntry.Id -eq $Global:__LastHistoryId) {
            # if entered 🦓 $null.Id -eq -1 == false            

        }
        else {
            # if entered then 🦓 that means: $Global:__LastHistoryId != -1
            $Result = '..'

            if ($LastHistoryEntry.CommandLine) {
                #🦓 if $null , therefore: false
                $CommandLine = $LastHistoryEntry.CommandLine
            }
            else {
                $CommandLine = ""
            }
        }
    }
    $Result += $Global:__VSCodeOriginalPrompt.Invoke()
    $Global:__LastHistoryId = $LastHistoryEntry.Id
    # 🦓 $global:__lastHistoryId = $null 
    return $Result
}
caparkaya commented 1 year ago

Thank you @ninmonkey , I'm not much familiar with this process but I understand that you were able to repro this issue. Should I propose a code-based solution & open a pull-request myself or will someone from the dev-team do this within a time-frame upon availability? I may not be able to see how my proposed solution will affect in the big picture, that's the reason of my hesitation.

andyleejordan commented 1 year ago

This should be resolved. The problem was actually the use of Set-StrictMode -Version Latest being incompatible with how the VS Code team wrote their shell integration script. We worked around it by adding:

    # NOTE: We disable strict mode for the scope of this function because it unhelpfully throws an
    # error when $LastHistoryEntry is null, and is not otherwise useful.
    Set-StrictMode -Off

to the prompt wrapper in that script.

ghost commented 1 year ago

This issue has been marked as answered and has not had any activity in a day. It has been automatically closed for housekeeping purposes.