Skip to content

Instantly share code, notes, and snippets.

@daxian-dbw
Created April 13, 2019 22:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daxian-dbw/cd73372bda66e562a67ad85637ced5a9 to your computer and use it in GitHub Desktop.
Save daxian-dbw/cd73372bda66e562a67ad85637ced5a9 to your computer and use it in GitHub Desktop.
Synchronize the loaded modules and global scope variables between two Runspaces
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PowerShellStandard.Library" Version="5.1.0" />
</ItemGroup>
</Project>
using System;
using System.Collections.Generic;
using System.Management.Automation;
public class RunspaceSynchronizer
{
public static bool SourceActionEnabled = false;
private static bool TargetActionEnabled = false;
// 'moduleCache' keeps track of all modules imported in the source Runspace.
// when there is a `Import-Module -Force`, the new module object would be a
// different instance with different hashcode, so we can tell if there is a
// force loading of an already loaded module.
private static HashSet<PSModuleInfo> moduleCache = new HashSet<PSModuleInfo>();
// 'variableCache' keeps all global scope variable names and their value type.
// As long as the value type doesn't change, we don't need to update the variable
// in the target Runspace, because all tab completion needs is the type information.
private static Dictionary<string, Type> variableCache = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
public static List<PSModuleInfo> moduleToImport = new List<PSModuleInfo>();
public static List<PSVariable> variablesToSet = new List<PSVariable>();
private static EngineIntrinsics sourceEngineIntrinsics;
private static EngineIntrinsics targetEngineIntrinsics;
private static object syncObj = new object();
private static bool sourceEventSubscribed = false;
private static bool targetEventSubscribed = false;
private static void CollectSourceRunspaceState(object sender, PSEventArgs args)
{
if (!SourceActionEnabled)
{
return;
}
try
{
// Maybe also track the latest history item id ($h = Get-History -Count 1; $h.Id)
// to make sure we do the collection only if there was actually any input.
var newOrChangedModules = new List<PSModuleInfo>();
var newOrChangedVars = new List<PSVariable>();
var results = sourceEngineIntrinsics.InvokeCommand.InvokeScript("Get-Module");
foreach (var res in results)
{
var module = (PSModuleInfo) res.BaseObject;
if (moduleCache.Add(module))
{
newOrChangedModules.Add(module);
}
}
// Process variables to get those that need to be updated.
// - first filter out the built-in variables.
// - check against the existing variable cache, if the value type of a variable name remains the same,
// then no need to update the variable.
// - finally get a dictionary of new/updated variables.
if (newOrChangedModules.Count == 0 && newOrChangedVars.Count == 0)
{
return;
}
lock (syncObj)
{
moduleToImport.AddRange(newOrChangedModules);
variablesToSet.AddRange(newOrChangedVars);
}
// Enable the action in target Runspace
TargetActionEnabled = true;
} catch (Exception ex) {
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
SourceActionEnabled = false;
}
private static void UpdateTargetRunspaceState(object sender, PSEventArgs args)
{
if (!TargetActionEnabled)
{
return;
}
List<PSModuleInfo> newOrChangedModules;
List<PSVariable> newOrChangedVars;
lock (syncObj)
{
newOrChangedModules = new List<PSModuleInfo>(moduleToImport);
newOrChangedVars = new List<PSVariable>(variablesToSet);
moduleToImport.Clear();
variablesToSet.Clear();
}
if (newOrChangedModules.Count > 0)
{
// Import the modules with -Force
}
if (newOrChangedVars.Count > 0)
{
// Set or update the variables.
}
TargetActionEnabled = false;
}
public static PSEventSubscriber SubscribeOnIdleEvent(EngineIntrinsics engineIntrinsics, bool isSourceRunspace)
{
PSEventReceivedEventHandler handler = null;
if (isSourceRunspace)
{
if (sourceEventSubscribed)
{
throw new InvalidOperationException("OnIdle even already subscribed in source Runspace.");
}
// Save the engineIntrinsics from source Runspace for later use.
handler = (PSEventReceivedEventHandler) CollectSourceRunspaceState;
sourceEngineIntrinsics = engineIntrinsics;
sourceEventSubscribed = true;
}
else
{
if (targetEventSubscribed)
{
throw new InvalidOperationException("OnIdle even already subscribed in target Runspace.");
}
// Save the engineIntrinsics from target Runspace for later use.
handler = (PSEventReceivedEventHandler) UpdateTargetRunspaceState;
targetEngineIntrinsics = engineIntrinsics;
targetEventSubscribed = true;
}
return engineIntrinsics.Events.SubscribeEvent(
source: null,
eventName: null,
sourceIdentifier: PSEngineEvent.OnIdle.ToString(),
data: null,
handlerDelegate: handler,
supportEvent: true,
forwardEvent: false);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment