Skip to content

Commit 63af1d3

Browse files
Merge branch 'runspaceVariableObject' into development
2 parents 75d619b + 4e66976 commit 63af1d3

10 files changed

Lines changed: 279 additions & 7 deletions

File tree

PSFramework/changelog.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
- Upd: Logging component logs in separate language than localized messages to screen / userinteraction
99
- Upd: Import-PSFLocalizedString now accepts wildcard path patterns that resovle to multiple files.
1010
- Upd: Adding tab completion for `Register-PSFTeppArgumentCompleter`
11-
- fix: Missing localization strings
12-
11+
- fix: Missing localization strings - Fix: Missing tab completion for modules that register localized strings
12+
1313
## 0.10.30.165 : 2018-12-01
1414
- New: Command Join-PSFPath performs multi-segment path joins and path normalization
1515
- New: Command Remove-PSFAlias deletes global aliases

PSFramework/internal/scripts/environment.ps1

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,7 @@ if (($PSVersionTable.PSVersion.Major -ge 6) -and ($PSVersionTable.OS -notlike "*
8787

8888
if (-not ([PSFramework.Message.LogHost]::LoggingPath)) { [PSFramework.Message.LogHost]::LoggingPath = $script:path_Logging }
8989

90-
[PSFramework.PSFCore.PSFCoreHost]::ModuleRoot = $script:ModuleRoot
90+
[PSFramework.PSFCore.PSFCoreHost]::ModuleRoot = $script:ModuleRoot
91+
# Run the library initialization logic
92+
# Needed before the configuration system loads
93+
[PSFramework.PSFCore.PSFCoreHost]::Initialize()

PSFramework/internal/scripts/removalEvent.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ $PSF_OnRemoveScript = {
44
if ([runspace]::DefaultRunspace.Id -eq 1)
55
{
66
Get-PSFRunspace | Stop-PSFRunspace
7+
[PSFramework.PSFCore.PSFCoreHost]::Uninitialize()
78
}
89

910
# Properly disconnect all remote sessions still held open
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
Import-LocalizedString -Path "$script:ModuleRoot\en-us\*.psd1" -Module PSFramework -Language 'en-US'
22

3-
43
$script:strings = Get-PSFLocalizedString -Module PSFramework
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
Register-PSFTeppScriptblock -Name PSFramework-Input-Object -ScriptBlock {
2+
[System.Management.Automation.Language.PipelineAst]$pipelineAst = $commandAst.parent
3+
$index = $pipelineAst.PipelineElements.IndexOf($commandAst)
4+
5+
#region If it's the first command
6+
if ($index -lt 1)
7+
{
8+
return
9+
}
10+
#endregion If it's the first command
11+
12+
$properties = @()
13+
$constraintsPositive = @()
14+
15+
#region Process pre-commands
16+
$inputIndex = $index - 1
17+
:main while ($true)
18+
{
19+
if ($pipelineAst.PipelineElements[$inputIndex].CommandElements)
20+
{
21+
# Resolve command and fail if it breaks
22+
$commandString = $pipelineAst.PipelineElements[$inputIndex].CommandElements[0].Value
23+
if ($commandString -eq "?") { $commandString = (Get-Alias -Name "?").ResolvedCommand.Name }
24+
$command = Get-Command $commandString -ErrorAction Ignore
25+
if ($command -is [System.Management.Automation.AliasInfo]) { $command = $command.ResolvedCommand }
26+
if (-not $command) { break }
27+
28+
switch ($command.Name)
29+
{
30+
'Where-Object' { $inputIndex = $inputIndex - 1; continue main }
31+
'Tee-Object' { $inputIndex = $inputIndex - 1; continue main }
32+
'Sort-Object' { $inputIndex = $inputIndex - 1; continue main }
33+
#region Select-Object
34+
'Select-Object'
35+
{
36+
$firstAst = $pipelineAst.PipelineElements[$inputIndex].CommandElements | Where-Object { $_ -is [System.Management.Automation.Language.ArrayLiteralAst] } | Select-Object -First 1
37+
38+
foreach ($element in $firstAst.Elements)
39+
{
40+
switch ($element.GetType().FullName)
41+
{
42+
'System.Management.Automation.Language.StringConstantExpressionAst'
43+
{
44+
$constraintsPositive += $element.Value
45+
if ($element.Value -notmatch "\*") { $properties += $element.Value }
46+
}
47+
'System.Management.Automation.Language.HashtableAst'
48+
{
49+
$constraintsPositive += ($element.KeyValuePairs | Where-Object Item1 -Match '^N$|^Name$' | Select-Object -First 1).Item2.ToString().Trim('"')
50+
$properties += ($element.KeyValuePairs | Where-Object Item1 -Match '^N$|^Name$' | Select-Object -First 1).Item2.ToString().Trim('"')
51+
}
52+
}
53+
}
54+
$inputIndex = $inputIndex - 1;
55+
continue main
56+
}
57+
#endregion Select-Object
58+
#region Select-PSFObject
59+
'Select-PSFObject'
60+
{
61+
$firstAst = $pipelineAst.PipelineElements[$inputIndex].CommandElements | Where-Object { $_ -is [System.Management.Automation.Language.ArrayLiteralAst] } | Select-Object -First 1
62+
foreach ($element in $firstAst.Elements)
63+
{
64+
switch ($element.GetType().FullName)
65+
{
66+
"System.Management.Automation.Language.StringConstantExpressionAst"
67+
{
68+
$par = [PSFramework.Parameter.SelectParameter]$element.Value
69+
if ($par.Value -match "\*") { $constraintsPositive += $par.Value }
70+
else
71+
{
72+
if ($par.Value -is [System.String])
73+
{
74+
$properties += $par.Value
75+
$constraintsPositive += $par.Value
76+
}
77+
else
78+
{
79+
$properties += $par.Value["Name"]
80+
$constraintsPositive += $par.Value["Name"]
81+
}
82+
}
83+
}
84+
"System.Management.Automation.Language.HashtableAst"
85+
{
86+
$properties += ($element.KeyValuePairs | Where-Object Item1 -Match '^N$|^Name$' | Select-Object -First 1).Item2.ToString().Trim('"')
87+
$constraintsPositive += ($element.KeyValuePairs | Where-Object Item1 -Match '^N$|^Name$' | Select-Object -First 1).Item2.ToString().Trim('"')
88+
}
89+
}
90+
}
91+
$inputIndex = $inputIndex - 1;
92+
}
93+
#endregion Select-PSFObject
94+
default { break main }
95+
}
96+
}
97+
98+
else
99+
{
100+
break
101+
}
102+
}
103+
104+
# Catch moving through _all_ options in the pipeline
105+
if ($inputIndex -lt 0) { return $properties }
106+
#endregion Process pre-commands
107+
108+
109+
#region Input from command
110+
if ($pipelineAst.PipelineElements[$inputIndex].CommandElements)
111+
{
112+
if ($command = Get-Command $pipelineAst.PipelineElements[$inputIndex].CommandElements[0].Value -ErrorAction Ignore)
113+
{
114+
switch ($command.Name)
115+
{
116+
#region Default for commands
117+
default
118+
{
119+
foreach ($type in $command.OutputType.Type)
120+
{
121+
switch ($type.FullName)
122+
{
123+
'System.IO.FileInfo'
124+
{
125+
$properties += ($type.GetMembers("Instance, Public") | Where-Object MemberType -match "Field|Property").Name
126+
$properties += 'PSChildName', 'PSDrive', 'PSIsContainer', 'PSParentPath', 'PSPath', 'PSProvider', 'BaseName'
127+
break
128+
}
129+
'System.IO.DirectoryInfo'
130+
{
131+
$properties += ($type.GetMembers("Instance, Public") | Where-Object MemberType -match "Field|Property").Name
132+
$properties += 'PSChildName', 'PSDrive', 'PSIsContainer', 'PSParentPath', 'PSPath', 'PSProvider', 'BaseName', 'VersionInfo'
133+
break
134+
}
135+
default { $properties += ($type.GetMembers("Instance, Public") | Where-Object MemberType -match "Field|Property").Name }
136+
}
137+
}
138+
}
139+
#endregion Default for commands
140+
}
141+
}
142+
}
143+
#endregion Input from command
144+
145+
#region Input from Variable
146+
if ($pipelineAst.PipelineElements[$inputIndex].Expression -and $pipelineAst.PipelineElements[0].Expression[0].VariablePath)
147+
{
148+
$properties += ((Get-Variable -Name $pipelineAst.PipelineElements[0].Expression[0].VariablePath.UserPath -ValueOnly) | Select-Object -First 1 | Get-Member -MemberType Properties).Name
149+
}
150+
#endregion Input from Variable
151+
152+
$properties | Select-Object -Unique | Sort-Object | ForEach-Object {
153+
if (-not $constraintsPositive) { $_ }
154+
foreach ($constraint in $constraintsPositive)
155+
{
156+
if ($_ -like $constraint)
157+
{
158+
$_
159+
break
160+
}
161+
}
162+
}
163+
}

PSFramework/internal/tepp/tepp-assignment.ps1

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Register-PSFTeppArgumentCompleter -Command Get-PSFLicense -Parameter Filter -Nam
2828

2929
#region Localization
3030
Register-PSFTeppArgumentCompleter -Command Import-PSFLocalizedString -Parameter Language -Name 'PSFramework-LanguageNames'
31+
Register-PSFTeppArgumentCompleter -Command Get-PSFLocalizedString -Parameter Module -Name 'PSFramework-LocalizedStrings-Modules'
3132
Register-PSFTeppArgumentCompleter -Command Get-PSFLocalizedString -Parameter Name -Name 'PSFramework-LocalizedStrings-Names'
3233
#endregion Localization
3334

@@ -67,5 +68,11 @@ Register-PSFTeppArgumentCompleter -Command Register-PSFTeppArgumentCompleter -Pa
6768
#endregion Tab Completion
6869

6970
#region Utility
71+
Register-PSFTeppArgumentCompleter -Command Select-PSFObject -Parameter Property -Name PSFramework-Input-Object
72+
Register-PSFTeppArgumentCompleter -Command Select-PSFObject -Parameter ExpandProperty -Name PSFramework-Input-Object
73+
Register-PSFTeppArgumentCompleter -Command Select-PSFObject -Parameter ExcludeProperty -Name PSFramework-Input-Object
74+
Register-PSFTeppArgumentCompleter -Command Select-PSFObject -Parameter ShowProperty -Name PSFramework-Input-Object
75+
Register-PSFTeppArgumentCompleter -Command Select-PSFObject -Parameter ShowExcludeProperty -Name PSFramework-Input-Object
76+
7077
Register-PSFTeppArgumentCompleter -Command Resolve-PSFPath -Parameter Provider -Name 'PSFramework-utility-psprovider'
7178
#endregion Utility

library/PSFramework/PSFCore/PSFCoreHost.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,33 @@ public static string ModuleRoot
2929
}
3030
}
3131
private static string _ModuleRoot;
32+
33+
/// <summary>
34+
/// Initializes the PSFramework library.
35+
/// Required for some components to work correctly.
36+
/// </summary>
37+
public static void Initialize()
38+
{
39+
if (_Initialized)
40+
return;
41+
_Initialized = true;
42+
43+
// Initialization logic goes here
44+
}
45+
private static bool _Initialized = false;
46+
47+
/// <summary>
48+
/// Reverses the initialization of the PSFramework library.
49+
/// Should be called when destroying the main runspace
50+
/// </summary>
51+
public static void Uninitialize()
52+
{
53+
if (!_Initialized)
54+
return;
55+
56+
// De-Initiialization logic goes here
57+
58+
_Initialized = false;
59+
}
3260
}
3361
}

library/PSFramework/Runspace/RunspaceBoundValue.cs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System;
1+
using PSFramework.Utility;
2+
using System;
23
using System.Collections.Generic;
34
using System.Linq;
45
using System.Management.Automation.Runspaces;
@@ -10,7 +11,7 @@ namespace PSFramework.Runspace
1011
/// <summary>
1112
/// Wrapper class that offers the tools to make Values runspace specific
1213
/// </summary>
13-
public class RunspaceBoundValue
14+
public class RunspaceBoundValue : IDisposable
1415
{
1516
/// <summary>
1617
/// Whether the defautl value should be offered when asking from a runspace without custom settings
@@ -49,9 +50,63 @@ public object Value
4950
}
5051
}
5152

53+
/// <summary>
54+
/// Removes all value entries whose corresponding Runspace has been destroyed
55+
/// </summary>
5256
public void PurgeExpired()
5357
{
54-
System.Management.Automation.Runspaces.Runspace.GetRunspaces(null);
58+
// Store IDs first, so parallel access is not an issue and a new value gets accidentally discarded
59+
Guid[] IDs = Values.Keys.ToArray();
60+
ICollection<System.Management.Automation.Runspaces.Runspace> runspaces = UtilityHost.GetRunspaces();
61+
ICollection<Guid> runspaceIDs = (ICollection<Guid>)runspaces.Select(o => o.InstanceId);
62+
63+
foreach (Guid ID in IDs)
64+
if (!runspaceIDs.Contains(ID))
65+
Values.Remove(ID);
66+
}
67+
68+
/// <summary>
69+
/// Destruction logic, eliminating all data stored in the object.
70+
/// Since handles to this object are automatically stored and maintained, it is impossible to otherwise guarantee releasing the object's data for the GC.
71+
/// </summary>
72+
public void Dispose()
73+
{
74+
Values = new Dictionary<Guid, object>();
75+
DefaultValue = null;
76+
RunspaceHost._RunspaceBoundValues.Remove(this);
77+
}
78+
79+
/// <summary>
80+
/// Create an empty runspace bound value object
81+
/// </summary>
82+
public RunspaceBoundValue()
83+
: this(null, true)
84+
{
85+
86+
}
87+
88+
/// <summary>
89+
/// Create a runspace bound value object with its initial value
90+
/// </summary>
91+
/// <param name="Value">The object to set as the initial value</param>
92+
public RunspaceBoundValue(object Value)
93+
: this(Value, true)
94+
{
95+
96+
}
97+
98+
/// <summary>
99+
/// Create a runspace bound value object with its initial value
100+
/// </summary>
101+
/// <param name="Value">The object to set as the initial value</param>
102+
/// <param name="OfferDefaultValue">Whether the initial / default value should be offered when accessed from runspaces that do not have a runspace-local value</param>
103+
public RunspaceBoundValue(object Value, bool OfferDefaultValue)
104+
{
105+
this.Value = Value;
106+
this.OfferDefaultValue = OfferDefaultValue;
107+
108+
// Add to central list of runspacebound values
109+
RunspaceHost._RunspaceBoundValues.Add(this);
55110
}
56111
}
57112
}

library/PSFramework/Runspace/RunspaceHost.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,10 @@ public static class RunspaceHost
1616
/// The dictionary containing the definitive list of unique Runspace
1717
/// </summary>
1818
public static Dictionary<string, RunspaceContainer> Runspaces = new Dictionary<string, RunspaceContainer>();
19+
20+
/// <summary>
21+
/// List of all runspace bound values in use
22+
/// </summary>
23+
internal static List<RunspaceBoundValue> _RunspaceBoundValues = new List<RunspaceBoundValue>();
1924
}
2025
}

library/PSFramework/Utility/UtilityHost.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,17 @@ public static object GetExecutionContextFromTLS()
235235
return method.Invoke(null, BindingFlags.NonPublic | BindingFlags.Static, null, null, System.Globalization.CultureInfo.CurrentCulture);
236236
}
237237

238+
/// <summary>
239+
/// Returns the list of runspaces available in the process
240+
/// </summary>
241+
/// <returns>The lists of currently known runspaces</returns>
242+
public static ICollection<System.Management.Automation.Runspaces.Runspace> GetRunspaces()
243+
{
244+
Type runspaceType = typeof(System.Management.Automation.Runspaces.Runspace);
245+
MethodInfo method = runspaceType.GetMethod("get_RunspaceList", BindingFlags.Static | BindingFlags.NonPublic);
246+
return (ICollection < System.Management.Automation.Runspaces.Runspace > )method.Invoke(null, null);
247+
}
248+
238249
/// <summary>
239250
/// Removes an alias from the global list of aliases
240251
/// </summary>

0 commit comments

Comments
 (0)