Fix null reference when Microsoft.PowerShell.Utility
is loaded as a snapin
in hosting scenarios (#9404)
This commit is contained in:
parent
3bfca6d0fa
commit
2737e74d86
|
@ -66,12 +66,7 @@ namespace Microsoft.PowerShell.Commands
|
|||
/// </summary>
|
||||
protected override void BeginProcessing()
|
||||
{
|
||||
_mdOption = this.CommandInfo.Module.SessionState.PSVariable.GetValue("PSMarkdownOptionInfo", new PSMarkdownOptionInfo()) as PSMarkdownOptionInfo;
|
||||
|
||||
if (_mdOption == null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
_mdOption = PSMarkdownOptionInfoCache.Get(this.CommandInfo);
|
||||
|
||||
bool? supportsVT100 = this.Host?.UI.SupportsVirtualTerminal;
|
||||
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Management.Automation;
|
||||
using System.Management.Automation.Internal;
|
||||
using System.Management.Automation.Runspaces;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.PowerShell.MarkdownRender;
|
||||
|
@ -124,7 +126,6 @@ namespace Microsoft.PowerShell.Commands
|
|||
private const string IndividualSetting = "IndividualSetting";
|
||||
private const string InputObjectParamSet = "InputObject";
|
||||
private const string ThemeParamSet = "Theme";
|
||||
private const string MarkdownOptionInfoVariableName = "PSMarkdownOptionInfo";
|
||||
private const string LightThemeName = "Light";
|
||||
private const string DarkThemeName = "Dark";
|
||||
|
||||
|
@ -173,11 +174,11 @@ namespace Microsoft.PowerShell.Commands
|
|||
break;
|
||||
}
|
||||
|
||||
this.CommandInfo.Module.SessionState.PSVariable.Set(MarkdownOptionInfoVariableName, mdOptionInfo);
|
||||
var setOption = PSMarkdownOptionInfoCache.Set(this.CommandInfo, mdOptionInfo);
|
||||
|
||||
if (PassThru.IsPresent)
|
||||
{
|
||||
WriteObject(mdOptionInfo);
|
||||
WriteObject(setOption);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,7 +257,61 @@ namespace Microsoft.PowerShell.Commands
|
|||
/// </summary>
|
||||
protected override void EndProcessing()
|
||||
{
|
||||
WriteObject(this.CommandInfo.Module.SessionState.PSVariable.GetValue(MarkdownOptionInfoVariableName, new PSMarkdownOptionInfo()));
|
||||
WriteObject(PSMarkdownOptionInfoCache.Get(this.CommandInfo));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The class manages whether we should use a module scope variable or concurrent dictionary for storing the set PSMarkdownOptions.
|
||||
/// When we have a moduleInfo available we use the module scope variable.
|
||||
/// In case of built-in modules, they are loaded as snapins when we are hosting PowerShell.
|
||||
/// We use runspace Id as the key for the concurrent dictionary to have the functionality of separate settings per runspace.
|
||||
/// Force loading the module does not unload the nested modules and hence we cannot use IModuleAssemblyCleanup to remove items from the dictionary.
|
||||
/// Because of these reason, we continue using module scope variable when moduleInfo is available.
|
||||
/// </summary>
|
||||
internal static class PSMarkdownOptionInfoCache
|
||||
{
|
||||
private static ConcurrentDictionary<Guid, PSMarkdownOptionInfo> markdownOptionInfoCache;
|
||||
private const string MarkdownOptionInfoVariableName = "PSMarkdownOptionInfo";
|
||||
|
||||
static PSMarkdownOptionInfoCache()
|
||||
{
|
||||
markdownOptionInfoCache = new ConcurrentDictionary<Guid, PSMarkdownOptionInfo>();
|
||||
}
|
||||
|
||||
internal static PSMarkdownOptionInfo Get(CommandInfo command)
|
||||
{
|
||||
// If we have the moduleInfo then store are module scope variable
|
||||
if (command.Module != null)
|
||||
{
|
||||
return command.Module.SessionState.PSVariable.GetValue(MarkdownOptionInfoVariableName, new PSMarkdownOptionInfo()) as PSMarkdownOptionInfo;
|
||||
}
|
||||
|
||||
// If we don't have a moduleInfo, like in PowerShell hosting scenarios, use a concurrent dictionary.
|
||||
if (markdownOptionInfoCache.TryGetValue(Runspace.DefaultRunspace.InstanceId, out PSMarkdownOptionInfo cachedOption))
|
||||
{
|
||||
// return the cached options for the runspaceId
|
||||
return cachedOption;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no option cache so cache and return the default PSMarkdownOptionInfo
|
||||
var newOptionInfo = new PSMarkdownOptionInfo();
|
||||
return markdownOptionInfoCache.GetOrAdd(Runspace.DefaultRunspace.InstanceId, newOptionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
internal static PSMarkdownOptionInfo Set(CommandInfo command, PSMarkdownOptionInfo optionInfo)
|
||||
{
|
||||
// If we have the moduleInfo then store are module scope variable
|
||||
if (command.Module != null)
|
||||
{
|
||||
command.Module.SessionState.PSVariable.Set(MarkdownOptionInfoVariableName, optionInfo);
|
||||
return optionInfo;
|
||||
}
|
||||
|
||||
// If we don't have a moduleInfo, like in PowerShell hosting scenarios with modules loaded as snapins, use a concurrent dictionary.
|
||||
return markdownOptionInfoCache.AddOrUpdate(Runspace.DefaultRunspace.InstanceId, optionInfo, (key, oldvalue) => optionInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -429,36 +429,6 @@ bool function()`n{`n}
|
|||
}
|
||||
|
||||
|
||||
$options.Link | Should -BeExactly "[4;38;5;117m"
|
||||
$options.Image | Should -BeExactly "[33m"
|
||||
$options.EmphasisBold | Should -BeExactly "[1m"
|
||||
$options.EmphasisItalics | Should -BeExactly "[36m"
|
||||
}
|
||||
|
||||
It "Verify PSMarkdownOptionInfo is defined in module scope" {
|
||||
|
||||
$PSMarkdownOptionInfo | Should -BeNullOrEmpty
|
||||
|
||||
$mod = Get-Module Microsoft.PowerShell.Utility
|
||||
$options = & $mod { $PSMarkdownOptionInfo }
|
||||
|
||||
$options.Header1 | Should -BeExactly "[7m"
|
||||
$options.Header2 | Should -BeExactly "[4;93m"
|
||||
$options.Header3 | Should -BeExactly "[4;94m"
|
||||
$options.Header4 | Should -BeExactly "[4;95m"
|
||||
$options.Header5 | Should -BeExactly "[4;96m"
|
||||
$options.Header6 | Should -BeExactly "[4;97m"
|
||||
|
||||
if($IsMacOS)
|
||||
{
|
||||
$options.Code | Should -BeExactly "[107;95m"
|
||||
}
|
||||
else
|
||||
{
|
||||
$options.Code | Should -BeExactly "[48;2;155;155;155;38;2;30;30;30m"
|
||||
}
|
||||
|
||||
|
||||
$options.Link | Should -BeExactly "[4;38;5;117m"
|
||||
$options.Image | Should -BeExactly "[33m"
|
||||
$options.EmphasisBold | Should -BeExactly "[1m"
|
||||
|
@ -546,4 +516,96 @@ bool function()`n{`n}
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Context "Hosted PowerShell scenario" {
|
||||
|
||||
It 'ConvertFrom-Markdown gets expected output when run in hosted powershell' {
|
||||
|
||||
try {
|
||||
$pool = [runspacefactory]::CreateRunspacePool(1, 2, $Host)
|
||||
$pool.Open()
|
||||
|
||||
$ps = [powershell]::Create()
|
||||
$ps.RunspacePool = $pool
|
||||
$ps.AddScript({
|
||||
$output = '# test' | ConvertFrom-Markdown
|
||||
$output.Html.trim()
|
||||
})
|
||||
|
||||
$output = $ps.Invoke()
|
||||
|
||||
$output | Should -BeExactly '<h1 id="test">test</h1>'
|
||||
} finally {
|
||||
$ps.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
It 'Get-MarkdownOption gets default values when run in hosted powershell' {
|
||||
|
||||
try {
|
||||
$ps = [powershell]::Create()
|
||||
$ps.AddScript( {
|
||||
Get-MarkdownOption -ErrorAction Stop
|
||||
})
|
||||
|
||||
$options = $ps.Invoke()
|
||||
|
||||
$options | Should -Not -BeNullOrEmpty
|
||||
$options.Header1 | Should -BeExactly "[7m"
|
||||
$options.Header2 | Should -BeExactly "[4;93m"
|
||||
$options.Header3 | Should -BeExactly "[4;94m"
|
||||
$options.Header4 | Should -BeExactly "[4;95m"
|
||||
$options.Header5 | Should -BeExactly "[4;96m"
|
||||
$options.Header6 | Should -BeExactly "[4;97m"
|
||||
|
||||
if ($IsMacOS) {
|
||||
$options.Code | Should -BeExactly "[107;95m"
|
||||
} else {
|
||||
$options.Code | Should -BeExactly "[48;2;155;155;155;38;2;30;30;30m"
|
||||
}
|
||||
|
||||
$options.Link | Should -BeExactly "[4;38;5;117m"
|
||||
$options.Image | Should -BeExactly "[33m"
|
||||
$options.EmphasisBold | Should -BeExactly "[1m"
|
||||
$options.EmphasisItalics | Should -BeExactly "[36m"
|
||||
}
|
||||
finally {
|
||||
$ps.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
It 'Set-MarkdownOption sets values when run in hosted powershell' {
|
||||
|
||||
try {
|
||||
$ps = [powershell]::Create()
|
||||
$ps.AddScript( {
|
||||
Set-MarkdownOption -Header1Color '[93m' -ErrorAction Stop -PassThru
|
||||
})
|
||||
|
||||
$options = $ps.Invoke()
|
||||
|
||||
$options | Should -Not -BeNullOrEmpty
|
||||
$options.Header1 | Should -BeExactly "[93m"
|
||||
$options.Header2 | Should -BeExactly "[4;93m"
|
||||
$options.Header3 | Should -BeExactly "[4;94m"
|
||||
$options.Header4 | Should -BeExactly "[4;95m"
|
||||
$options.Header5 | Should -BeExactly "[4;96m"
|
||||
$options.Header6 | Should -BeExactly "[4;97m"
|
||||
|
||||
if ($IsMacOS) {
|
||||
$options.Code | Should -BeExactly "[107;95m"
|
||||
} else {
|
||||
$options.Code | Should -BeExactly "[48;2;155;155;155;38;2;30;30;30m"
|
||||
}
|
||||
|
||||
$options.Link | Should -BeExactly "[4;38;5;117m"
|
||||
$options.Image | Should -BeExactly "[33m"
|
||||
$options.EmphasisBold | Should -BeExactly "[1m"
|
||||
$options.EmphasisItalics | Should -BeExactly "[36m"
|
||||
}
|
||||
finally {
|
||||
$ps.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue