Skip to content

Instantly share code, notes, and snippets.

@robin-collins
Last active December 3, 2023 08:37
Show Gist options
  • Save robin-collins/2717aae05b8c21f283834b1cb590c898 to your computer and use it in GitHub Desktop.
Save robin-collins/2717aae05b8c21f283834b1cb590c898 to your computer and use it in GitHub Desktop.
powershell deferred execution - aka fast profile startup
$Deferred = {
. "/home/freddie/.local/share/chezmoi/PSHelpers/Console.ps1"
. "/home/freddie/.local/share/chezmoi/PSHelpers/git_helpers.ps1"
# ...other slow code...
}
# https://seeminglyscience.github.io/powershell/2017/09/30/invocation-operators-states-and-scopes
$GlobalState = [psmoduleinfo]::new($false)
$GlobalState.SessionState = $ExecutionContext.SessionState
# to run our code asynchronously
$Runspace = [runspacefactory]::CreateRunspace($Host)
$Powershell = [powershell]::Create($Runspace)
$Runspace.Open()
$Runspace.SessionStateProxy.PSVariable.Set('GlobalState', $GlobalState)
# ArgumentCompleters are set on the ExecutionContext, not the SessionState
# Note that $ExecutionContext is not an ExecutionContext, it's an EngineIntrinsics 😡
$Private = [Reflection.BindingFlags]'Instance, NonPublic'
$ContextField = [Management.Automation.EngineIntrinsics].GetField('_context', $Private)
$Context = $ContextField.GetValue($ExecutionContext)
# Get the ArgumentCompleters. If null, initialise them.
$ContextCACProperty = $Context.GetType().GetProperty('CustomArgumentCompleters', $Private)
$ContextNACProperty = $Context.GetType().GetProperty('NativeArgumentCompleters', $Private)
$CAC = $ContextCACProperty.GetValue($Context)
$NAC = $ContextNACProperty.GetValue($Context)
if ($null -eq $CAC)
{
$CAC = [Collections.Generic.Dictionary[string, scriptblock]]::new()
$ContextCACProperty.SetValue($Context, $CAC)
}
if ($null -eq $NAC)
{
$NAC = [Collections.Generic.Dictionary[string, scriptblock]]::new()
$ContextNACProperty.SetValue($Context, $NAC)
}
# Get the AutomationEngine and ExecutionContext of the runspace
$RSEngineField = $Runspace.GetType().GetField('_engine', $Private)
$RSEngine = $RSEngineField.GetValue($Runspace)
$EngineContextField = $RSEngine.GetType().GetFields($Private) | Where-Object {$_.FieldType.Name -eq 'ExecutionContext'}
$RSContext = $EngineContextField.GetValue($RSEngine)
# Set the runspace to use the global ArgumentCompleters
$ContextCACProperty.SetValue($RSContext, $CAC)
$ContextNACProperty.SetValue($RSContext, $NAC)
$Wrapper = {
# Without a sleep, you get issues:
# - occasional crashes
# - prompt not rendered
# - no highlighting
# Assumption: this is related to PSReadLine.
# 20ms seems to be enough on my machine, but let's be generous - this is non-blocking
Start-Sleep -Milliseconds 200
. $GlobalState {. $Deferred; Remove-Variable Deferred}
}
$null = $Powershell.AddScript($Wrapper.ToString()).BeginInvoke()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment