Created
April 13, 2019 22:22
-
-
Save daxian-dbw/cd73372bda66e562a67ad85637ced5a9 to your computer and use it in GitHub Desktop.
Synchronize the loaded modules and global scope variables between two Runspaces
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<TargetFramework>netstandard2.0</TargetFramework> | |
</PropertyGroup> | |
<ItemGroup> | |
<PackageReference Include="PowerShellStandard.Library" Version="5.1.0" /> | |
</ItemGroup> | |
</Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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