Seemingly Science2017-09-30T19:35:53+00:00http://seeminglyscience.github.ioPatrick MeineckeSeeminglyScience@gmail.comInvocation Operators, States and Scopes2017-09-30T00:00:00+00:00http://seeminglyscience.github.io/powershell/2017/09/30/invocation-operators-states-and-scopes
<p>I’m going to start off with invocation operators, but don’t worry it’ll come around full circle. If
you’re here just for scopes, you can skip to the PSModuleInfo section.</p>
<h2 id="common-knowledge">Common Knowledge</h2>
<p>In general you can think of the invocation operators <code class="highlighter-rouge">&</code> and <code class="highlighter-rouge">.</code> as a way of telling PowerShell “run
whatever is after this symbol”. There are a few different ways you can use theses symbols.</p>
<h3 id="name-or-path">Name or Path</h3>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code>& .<span class="se">\P</span>ath<span class="se">\T</span>o<span class="se">\C</span>ommand.ps1
</code></pre>
</div>
<p>Invoke whatever is at this path. The path can be a script, an executable, or anything else that resolves as a command.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code>& <span class="nb">Get-ChildItem</span>
</code></pre>
</div>
<p>Invoke a command by name. You don’t typically need an operator to invoke a command, but what you
might not know is you can also use the <code class="highlighter-rouge">.</code> operator here to invoke a command without creating a new
scope.
<!--more-->
For example:</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="k">function </span>MyPrivateCommand <span class="o">{</span>
<span class="nv">$var</span> <span class="o">=</span> 10
<span class="o">}</span>
. MyPrivateCommand
<span class="nv">$var</span>
<span class="c1"># 10</span>
</code></pre>
</div>
<h3 id="invoke-a-scriptblock">Invoke a ScriptBlock</h3>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code>& <span class="o">{</span> <span class="nb">Get-ChildItem</span> <span class="o">}</span>
</code></pre>
</div>
<p>There’s a ton of ways to use this and it could probably be it’s own blog post. There is one use though,
that I don’t see a lot and want to talk about.</p>
<p>Let’s say you have a bunch of commands that output to the pipeline, but you really don’t care about
their output. You aren’t going to assign them to a variable, so you would typically use <code class="highlighter-rouge">Out-Null</code>
or <code class="highlighter-rouge">$null =</code> on each statement to ensure your pipeline remains clean. Instead, you can group those
commands in a single scriptblock and null them all at once.</p>
<p>Lets take <code class="highlighter-rouge">StringBuilder</code> for instance. This class returns an instance of itself with every method.
It does this so you can “chain” the methods together, like this.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nv">$sb</span> <span class="o">=</span> <span class="o">[</span>System.Text.StringBuilder]::new<span class="o">()</span>
<span class="nv">$sb</span>.Append<span class="o">(</span><span class="s2">"This"</span><span class="o">)</span>.AppendLine<span class="o">(</span><span class="s2">" is a string"</span><span class="o">)</span>.Append<span class="o">(</span><span class="s2">"Cool right?"</span><span class="o">)</span>.ToString<span class="o">()</span>
<span class="c1"># This is a string</span>
<span class="c1"># Cool right?</span>
</code></pre>
</div>
<p>But let’s say you can’t chain all the way through, you need to stop and check some things after you
append to see what comes next in the string. If you do that often enough, you end up with a lot of
lines you need to null.</p>
<p>Alternatively, just group them together and don’t worry about it.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nv">$sb</span> <span class="o">=</span> <span class="o">[</span>System.Text.StringBuilder]::new<span class="o">()</span>
<span class="nv">$isString</span> <span class="o">=</span> <span class="nv">$false</span>
<span class="nv">$null</span> <span class="o">=</span> & <span class="o">{</span>
<span class="nv">$sb</span>.Append<span class="o">(</span><span class="s2">"This is a"</span><span class="o">)</span>
<span class="k">if</span> <span class="o">(</span><span class="nv">$isString</span><span class="o">)</span> <span class="o">{</span>
<span class="nv">$sb</span>.Append<span class="o">(</span><span class="s2">" string!"</span><span class="o">)</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="nv">$sb</span>.Append<span class="o">(</span><span class="s2">"... I'm not sure actually"</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nv">$sb</span>.ToString<span class="o">()</span>
<span class="c1"># This is a... I'm not sure actually</span>
</code></pre>
</div>
<h2 id="less-common-knowledge">Less Common Knowledge</h2>
<h3 id="commandinfo-objects">CommandInfo objects</h3>
<p><code class="highlighter-rouge">CommandInfo</code> is the object type returned by the <code class="highlighter-rouge">Get-Command</code> cmdlet. I tend to use this when I need
to look a few different places for a command, or if I need to be very specific.</p>
<p>Lets say you’re looking for a executable. If they have it installed and in their <code class="highlighter-rouge">$env:PATH</code>, you want
to use that. If they don’t, you want to install it to the current folder.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="k">function </span>Get-DotNet <span class="o">{</span>
<span class="nv">$dotnet</span> <span class="o">=</span> <span class="nb">Get-Command </span>dotnet -ErrorAction Ignore
<span class="k">if</span> <span class="o">(</span>-not <span class="nv">$dotnet</span><span class="o">)</span> <span class="o">{</span>
<span class="c1"># Some code that saves the dotnet cli to ./dotnet</span>
<span class="nv">$dotnet</span> <span class="o">=</span> <span class="nb">Get-Command</span> ./dotnet/dotnet.exe -ErrorAction Ignore
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span>-not <span class="nv">$dotnet</span> -or <span class="nv">$dotnet</span>.Version -lt <span class="s1">'2.0.0'</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="s2">"Couldn't find dotnet!"</span>
<span class="o">}</span>
<span class="k">return</span> <span class="nv">$dotnet</span>
<span class="o">}</span>
& <span class="o">(</span>Get-DotNet<span class="o">)</span> build
</code></pre>
</div>
<p>At the end of <code class="highlighter-rouge">Get-DotNet</code> I don’t care where I got it from, or even what type of command it is. I
can call it all the same. You can use <code class="highlighter-rouge">Get-Command</code> to narrow down a command to a specific version,
from a specific module, in a specific path, of a specific command type and more.</p>
<h3 id="psmoduleinfo">PSModuleInfo</h3>
<p>Here’s where things get cool. I’m going to be honest though, this is not widely applicable. If you
read this and think “I can’t see myself ever using this” don’t worry, that’s normal. But a few of
you might be screaming with excitement.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nv">$module</span> <span class="o">=</span> Get-Module Pester
& <span class="nv">$module</span> <span class="o">{</span> <span class="nv">$ExecutionContext</span>.SessionState.Module <span class="o">}</span>
<span class="c1"># ModuleType Version Name ExportedCommands</span>
<span class="c1"># ---------- ------- ---- ----------------</span>
<span class="c1"># Script 4.0.6 pester {Add-AssertionOperator,</span>
</code></pre>
</div>
<p>This one works a little differently. It isn’t <em>invoking</em> the module, instead it’s acting as a
modifier. It basically tells PowerShell to run whatever comes after the module as if it was ran
from within the module.</p>
<p>More specifically, it replaces the <code class="highlighter-rouge">SessionStateInternal</code> of the command with the <code class="highlighter-rouge">SessionStateInternal</code>
of the modules <code class="highlighter-rouge">SessionState</code>.</p>
<h4 id="non-exported-commands">Non-Exported Commands</h4>
<p>Most modules have private functions, classes, variables, etc. that it uses internally, but aren’t meant
to be used from outside the module. Using this method, you can do just that.</p>
<p>Let me be clear here though. If you do use them, <strong>you’re on your own</strong>. They are private for a reason.
Maybe they are dangerous, maybe they are buggy in less than perfect scenarios, or maybe they are just
obtuse to use. Whatever the reason, they are <strong>not supported</strong>. If it’s your module though, go nuts.
It’s great for troubleshooting and testing.</p>
<p>Anyway, lets look at Pester again. If you’ve ever glanced at Pester’s source, you’ve probably seen
they make a table of “safe” commands that aren’t mocked. Lets say you wanted to make sure a command
was resolved correctly in that table.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code>& <span class="o">(</span><span class="nb">gmo </span>Pester<span class="o">)</span> <span class="o">{</span> <span class="nv">$SafeCommands</span><span class="o">[</span><span class="s1">'New-Object'</span><span class="o">]</span> <span class="o">}</span>
<span class="c1"># CommandType Name Version Source</span>
<span class="c1"># ----------- ---- ------- ------</span>
<span class="c1"># Cmdlet New-Object 3.1.0.0 Microsoft.PowerShell.Utility</span>
</code></pre>
</div>
<p>You can also call functions, change the value of script scope variables, and even return classes
without having to use the <code class="highlighter-rouge">using module</code> syntax.</p>
<h4 id="vessels-for-state">Vessels for State</h4>
<p>Here’s a fun fact, the <code class="highlighter-rouge">SessionState</code> property on <code class="highlighter-rouge">PSModuleInfo</code> is <em>writeable</em>. This means you can
throw any old <code class="highlighter-rouge">SessionState</code> into the module and you now have a portable, invokeable link to that state.
Even if the state isn’t from a module.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nv">$globalState</span> <span class="o">=</span> <span class="o">[</span>PSModuleInfo]::new<span class="o">(</span><span class="nv">$false</span><span class="o">)</span>
<span class="nv">$globalState</span>.SessionState <span class="o">=</span> <span class="nv">$ExecutionContext</span>.SessionState
<span class="nv">$myGlobalVar</span> <span class="o">=</span> <span class="s1">'Hello from global!'</span>
<span class="nv">$myGlobalVar</span>
<span class="c1"># Hello from global!</span>
<span class="nv">$null</span> <span class="o">=</span> <span class="o">[</span>PSModuleInfo]::new<span class="o">({</span>
<span class="nv">$myGlobalVar</span> <span class="o">=</span> <span class="s1">'Hello from a module!'</span>
<span class="o">})</span>
<span class="nv">$myGlobalVar</span>
<span class="c1"># Hello from global!</span>
<span class="nv">$null</span> <span class="o">=</span> <span class="o">[</span>PSModuleInfo]::new<span class="o">({</span>
. <span class="nv">$globalState</span> <span class="o">{</span> <span class="nv">$myGlobalVar</span> <span class="o">=</span> <span class="s1">'Hello from a module!'</span> <span class="o">}</span>
<span class="o">})</span>
<span class="nv">$myGlobalVar</span>
<span class="c1"># Hello from a module!</span>
</code></pre>
</div>
<p>In the first example, the variable changed in the module, but it didn’t change the original because
of the different <code class="highlighter-rouge">SessionState</code>.</p>
<h4 id="enter-a-modules-scope">Enter a Modules Scope</h4>
<p>Have you ever been troubleshooting a module and wished you could just enter the scope of the module,
and run commands from the prompt like you were in the <code class="highlighter-rouge">psm1</code>?</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code>. <span class="o">(</span><span class="nb">gmo </span>Pester<span class="o">)</span> <span class="o">{</span> <span class="nv">$Host</span>.EnterNestedPrompt<span class="o">()</span> <span class="o">}</span>
</code></pre>
</div>
<p>There you go, you’re now in the <code class="highlighter-rouge">psm1</code>. Run <code class="highlighter-rouge">exit</code> when you want to return to the global scope. Do you
instead wish that it worked more like you were in a function? Switch the <code class="highlighter-rouge">.</code> with <code class="highlighter-rouge">&</code>.</p>
<p>Some of you may have read that last sentence and thought “Wait what? That’s not how I thought dot
sourcing worked…” Keep reading, because that’s why I want to talk about what I call the
“PowerShell State Tree”.</p>
<h2 id="the-powershell-state-tree">The PowerShell State Tree</h2>
<p>Alright so I just made up the name “State Tree”, I don’t think there is an actual term that refers
to the whole thing. The rest <em>are</em> real terms though :)</p>
<div class="language-text highlighter-rouge"><pre class="highlight"><code>PowerShell Process
|------ ExecutionContext
| |------ SessionStateInternal
| | |------ SessionStateScope
| | |------ SessionStateScope
| | | |------ SessionStateScope
| | |------ SessionStateScope
| |
| |------ SessionStateInternal
| |------ SessionStateScope
|
|------ ExecutionContext
|------ SessionStateInternal
|------ SessionStateScope
</code></pre>
</div>
<h3 id="executioncontext">ExecutionContext</h3>
<p>For every runspace in a process, there is an <code class="highlighter-rouge">ExecutionContext</code>. This holds instances of a lot of
high level features you don’t ever need to worry about, like the help system, the parser, and most
importantly, all of our <code class="highlighter-rouge">SessionState</code> objects.</p>
<p>The public facing version of this object is <code class="highlighter-rouge">EngineIntrinsics</code> which is confusingly returned from the
variable <code class="highlighter-rouge">$ExecutionContext</code>.</p>
<h3 id="sessionstateinternal">SessionStateInternal</h3>
<p>For every module in a runspace, there is a <code class="highlighter-rouge">SessionStateInternal</code>. Plus one “Top Level” or “Global”
<code class="highlighter-rouge">SessionStateInternal</code>. These internally hold references to several <code class="highlighter-rouge">SessionStateScope</code> objects.</p>
<ul>
<li><strong>Global</strong> - Typically this scope is the link back to the top level state.</li>
<li><strong>Script</strong> - Typically the psm1 file in a module or the first child scope otherwise. If no child
scopes are created (like when running commands from the console) this scope is the same as global.</li>
<li><strong>Current</strong> - The newest child scope, can be the same as script or global.</li>
</ul>
<p>The public facing version of this object is <code class="highlighter-rouge">SessionState</code>. It is the object returned from
<code class="highlighter-rouge">$ExecutionContext.SessionState</code>, <code class="highlighter-rouge">PSModuleInfo.SessionState</code> and pretty much anything else that
calls itself “SessionState”.</p>
<h3 id="sessionstatescope">SessionStateScope</h3>
<p>I could probably write a whole post on scopes alone. Scopes are like a moment in time during the
invocation of a script. Many scopes are created during a single script execution.</p>
<p>At the very least, any time a <code class="highlighter-rouge">scriptblock</code> is invoked, a new scope is created by default. Keep in
mind, almost everything is boiled down or brought up to a <code class="highlighter-rouge">scriptblock</code> at some point in the
invocation cycle. Functions, PowerShell class methods, modules, scripts, even the text you type at
the prompt is or will be a <code class="highlighter-rouge">scriptblock</code> at some point. Cmdlets (compiled commands like <code class="highlighter-rouge">Get-ChildItem</code>)
do <strong>not</strong> create scopes.</p>
<p>When a scope is created, it is added as a child to the <code class="highlighter-rouge">Current</code> scope of the <strong>session state</strong> the command
<strong>belongs to</strong>. This small detail causes a lot of common misconceptions about how scopes work. When you
first hear of scopes, you initially picture something more like the call stack. You assume that if
you call a function from a module then the scope that gets created will be a child of the scope you
are calling it in. But instead, it’s a part of it’s own separate tree of scopes entirely.</p>
<p>There is no public facing version of this object.</p>
<h3 id="what-is-dot-sourcing-really">What is Dot Sourcing Really</h3>
<p>Towards the start of this post, when describing the dot source operator I described it as “a way to
invoke a command without creating a new scope”. The phrasing used was very specific and purposeful.</p>
<p>When you dot source something it runs in the scope that is marked as the current scope in the
session state the command is from. That part is very important. It means that you aren’t creating
a new scope, <strong>but</strong> you may still <strong>change</strong> scopes. Thankfully, invoking modules provides a very
easy way to demonstrate this.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nv">$pester</span> <span class="o">=</span> Get-Module Pester
<span class="c1"># Creates a new scope in the Pester module. This is the same context</span>
<span class="c1"># a function from the module would run in.</span>
& <span class="nv">$pester</span> <span class="o">{</span> <span class="nv">$test</span>; <span class="nv">$test</span> <span class="o">=</span> <span class="s1">'test'</span> <span class="o">}</span>
& <span class="nv">$pester</span> <span class="o">{</span> <span class="nv">$test</span>; <span class="nv">$test</span> <span class="o">=</span> <span class="s1">'test'</span> <span class="o">}</span>
</code></pre>
</div>
<p>Both of the above calls return nothing because test was defined in a child scope that stops
existing as soon as the command finishes.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nv">$pester</span> <span class="o">=</span> Get-Module Pester
<span class="c1"># What happens if we add another in the middle that is dot sourced?</span>
& <span class="nv">$pester</span> <span class="o">{</span> <span class="nv">$test</span>; <span class="nv">$test</span> <span class="o">=</span> <span class="s1">'test'</span> <span class="o">}</span> <span class="c1"># Nothing</span>
. <span class="nv">$pester</span> <span class="o">{</span> <span class="nv">$test</span>; <span class="nv">$test</span> <span class="o">=</span> <span class="s1">'test'</span> <span class="o">}</span> <span class="c1"># Nothing</span>
& <span class="nv">$pester</span> <span class="o">{</span> <span class="nv">$test</span>; <span class="nv">$test</span> <span class="o">=</span> <span class="s1">'test'</span> <span class="o">}</span> <span class="c1"># test</span>
<span class="nv">$test</span> <span class="c1"># Nothing</span>
</code></pre>
</div>
<p>The first two still return nothing, but the third returns the value we set. This is because we dot
sourced the second, so no new scope was created, therefore <code class="highlighter-rouge">$test</code> was defined in the current scope
of the session state. This is just like if the code in the <code class="highlighter-rouge">scriptblock</code> was placed in the <code class="highlighter-rouge">psm1</code> file
directly.</p>
<p>The last line doesn’t return anything because it runs in the top level session state. Remember,
dot sourcing runs the command in what is marked as the current scope of the session state for the
command, it does <em>not</em> run the <em>literal</em> current scope.</p>
<h3 id="show-psstatetree">Show-PSStateTree</h3>
<p>One of the most challenging things when you are trying to learn about scopes is how difficult it is
to pin down exactly when one is being created. First, there is no public facing interface for scopes,
so you are limited to using a ton of reflection to even get to a representation of them. Second, almost
all of the things you do in PowerShell create a new scope, so how do you really know how many scopes
you created just trying to figure out if a scope was created?</p>
<p>I mentioned cmdlets don’t create a scope, so I made a small cmdlet to help track scopes. It will return
the hash code of several parts of the state tree. This won’t tell you much, but you can compare them
to see when they change, which ones stick around, etc.</p>
<p>Below is the same example as above, with this cmdlet added.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nv">$pester</span> <span class="o">=</span> Get-Module Pester
& <span class="nv">$pester</span> <span class="o">{</span> Show-PSStateTree; <span class="nv">$test</span>; <span class="nv">$test</span> <span class="o">=</span> <span class="s1">'test'</span> <span class="o">}</span>
<span class="c1"># ExecutionContext : 56919981</span>
<span class="c1"># TopLevelSessionState : 3678064</span>
<span class="c1"># EngineSessionState : 49566835</span>
<span class="c1"># GlobalScope : 62978787</span>
<span class="c1"># ModuleScope : 22936020</span>
<span class="c1"># ScriptScope : 22936020</span>
<span class="c1"># ParentScope : 22936020</span>
<span class="c1"># CurrentScope : 23035345</span>
. <span class="nv">$pester</span> <span class="o">{</span> Show-PSStateTree; <span class="nv">$test</span>; <span class="nv">$test</span> <span class="o">=</span> <span class="s1">'test'</span> <span class="o">}</span>
<span class="c1"># ExecutionContext : 56919981</span>
<span class="c1"># TopLevelSessionState : 3678064</span>
<span class="c1"># EngineSessionState : 49566835</span>
<span class="c1"># GlobalScope : 62978787</span>
<span class="c1"># ModuleScope : 22936020</span>
<span class="c1"># ScriptScope : 22936020</span>
<span class="c1"># ParentScope : 62978787</span>
<span class="c1"># CurrentScope : 22936020</span>
& <span class="nv">$pester</span> <span class="o">{</span> Show-PSStateTree; <span class="nv">$test</span>; <span class="nv">$test</span> <span class="o">=</span> <span class="s1">'test'</span> <span class="o">}</span>
<span class="c1"># ExecutionContext : 56919981</span>
<span class="c1"># TopLevelSessionState : 3678064</span>
<span class="c1"># EngineSessionState : 49566835</span>
<span class="c1"># GlobalScope : 62978787</span>
<span class="c1"># ModuleScope : 22936020</span>
<span class="c1"># ScriptScope : 22936020</span>
<span class="c1"># ParentScope : 22936020</span>
<span class="c1"># CurrentScope : 42591724</span>
<span class="c1"># test</span>
Show-PSStateTree; <span class="nv">$test</span>
<span class="c1"># ExecutionContext : 56919981</span>
<span class="c1"># TopLevelSessionState : 3678064</span>
<span class="c1"># EngineSessionState : 3678064</span>
<span class="c1"># GlobalScope : 62978787</span>
<span class="c1"># ModuleScope : 62978787</span>
<span class="c1"># ScriptScope : 62978787</span>
<span class="c1"># ParentScope : 0</span>
<span class="c1"># CurrentScope : 62978787</span>
</code></pre>
</div>
<p><a href="https://gist.github.com/SeeminglyScience/fc64a6228c115768926b13617c5f56d8">Here’s the Gist</a> if you want to play around with it. Copy and paste it into the console, save it
or save it as a file and run it, then test away.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nv">$typeDefinition</span> <span class="o">=</span> @<span class="s1">'
using System;
using System.Management.Automation;
using System.Reflection;
namespace PSStateTree.Commands
{
[OutputType(typeof(StateTreeInfo))]
[Cmdlet(VerbsCommon.Show, "PSStateTree")]
public class ShowPSStateTreeCommand : PSCmdlet
{
private struct StateTreeInfo
{
public int ExecutionContext;
public int TopLevelSessionState;
public int EngineSessionState;
public int GlobalScope;
public int ModuleScope;
public int ScriptScope;
public int ParentScope;
public int CurrentScope;
}
private const BindingFlags BINDING_FLAGS = BindingFlags.Instance | BindingFlags.NonPublic;
protected override void ProcessRecord()
{
StateTreeInfo result;
EngineIntrinsics engine = GetVariableValue("ExecutionContext") as EngineIntrinsics;
var context = engine
.GetType()
.GetTypeInfo()
.GetField("_context", BINDING_FLAGS)
.GetValue(engine);
var stateInternal = GetProperty("Internal", SessionState);
var currentScope = GetProperty("CurrentScope", stateInternal);
var parent = GetProperty("Parent", currentScope);
result.ParentScope = 0;
if (parent != null)
{
result.ParentScope = parent.GetHashCode();
}
result.ExecutionContext = context.GetHashCode();
result.EngineSessionState = GetProperty("EngineSessionState", context).GetHashCode();
result.TopLevelSessionState = GetProperty("TopLevelSessionState", context).GetHashCode();
result.CurrentScope = currentScope.GetHashCode();
result.ScriptScope = GetProperty("ScriptScope", stateInternal).GetHashCode();
result.ModuleScope = GetProperty("ModuleScope", stateInternal).GetHashCode();
result.GlobalScope = GetProperty("GlobalScope", stateInternal).GetHashCode();
WriteObject(result);
}
private object GetProperty(string propertyName, object source)
{
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentNullException("propertyName");
}
if (source == null)
{
throw new ArgumentNullException("source");
}
return source
.GetType()
.GetTypeInfo()
.GetProperty(propertyName, BINDING_FLAGS)
.GetValue(source);
}
}
}
'</span>@
<span class="k">if</span> <span class="o">(</span>-not <span class="o">(</span><span class="s1">'PSStateTree.Commands.ShowPSStateTreeCommand'</span> -as <span class="o">[</span><span class="nb">type</span><span class="o">]))</span> <span class="o">{</span>
<span class="nv">$module</span> <span class="o">=</span> New-Module -Name PSStateTree -ScriptBlock <span class="o">{</span>
<span class="nv">$types</span> <span class="o">=</span> Add-Type -TypeDefinition <span class="nv">$typeDefinition</span> -PassThru
Import-Module -Assembly <span class="nv">$types</span><span class="o">[</span>0].Assembly
Export-ModuleMember -Cmdlet Show-PSStateTree
<span class="o">}</span>
Import-Module <span class="nv">$module</span>
<span class="o">}</span>
</code></pre>
</div>
Formatting Objects without XML2017-04-20T00:00:00+00:00http://seeminglyscience.github.io/powershell/2017/04/20/formatting-objects-without-xml
<p>Custom formatting in PowerShell has always seemed like one of the most under utilized features of PowerShell to me.
And I understand why. It feels kind of bizarre to spend all this time writing PowerShell, creating a cool custom
object and then jumping into…XML.</p>
<p>The bad news is, what I’m about to tell you is still going to involve XML. The good news, you don’t
have to write it.</p>
<h2 id="pscontrol-classes">PSControl Classes</h2>
<p>One of the things I love the most about PowerShell is the pure discoverability it provides. You can
usually figure out what you need to do with just about any command, object, etc. That all gets a little
abstracted when you’re working through XML.</p>
<p>The PSControl object (and more importantly it’s child classes) give that discoverability back. Let’s
pipe <code class="highlighter-rouge">TableControl</code> to <code class="highlighter-rouge">Get-Member</code> so you can see what I mean.</p>
<pre><code class="language-raw">PS C:\> [System.Management.Automation.TableControl] | Get-Member -Static
TypeName: System.Management.Automation.TableControl
Name MemberType Definition
---- ---------- ----------
Create Method static System.Management.Automation.Table...
Equals Method static bool Equals(System.Object objA, Sy...
new Method System.Management.Automation.TableControl...
ReferenceEquals Method static bool ReferenceEquals(System.Object...
</code></pre>
<p>Let’s take a closer look at the <code class="highlighter-rouge">Create</code> static method.
<!--more--></p>
<pre><code class="language-raw">PS C:\> [System.Management.Automation.TableControl]::Create
OverloadDefinitions
-------------------
static System.Management.Automation.TableControlBuilder Create(bool outOfBand, bool autoSize, bool hideTableHeaders)
</code></pre>
<p>Now, I know I just went on about discoverability, but what this is leaving out is all of these parameters are
optional. So all we actually need to do is run <code class="highlighter-rouge">Create()</code>. Also, you may have noticed that returns a
different type, so let’s see what options it has.</p>
<pre><code class="language-raw">PS C:\> [System.Management.Automation.TableControl]::Create() | Get-Member
TypeName: System.Management.Automation.TableControlBuilder
Name MemberType Definition
---- ---------- ----------
AddHeader Method System.Management.Automation.TableCont...
EndTable Method System.Management.Automation.TableCont...
GroupByProperty Method System.Management.Automation.TableCont...
GroupByScriptBlock Method System.Management.Automation.TableCont...
StartRowDefinition Method System.Management.Automation.TableRowD...
</code></pre>
<p>So if you were to expand the definitions you would see a few of the methods return a copy of themselves,
meaning they are meant to be chained together. We also have a new builder, <code class="highlighter-rouge">TableRowDefinitionBuilder</code>.</p>
<p>I’m sure you get the idea by now, so I’m going to skip ahead a little and show you an example I made
for an existing type.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="c1"># For System.Reflection.RuntimeParameterInfo</span>
<span class="o">[</span>System.Management.Automation.TableControl]::Create<span class="o">()</span>.
GroupByProperty<span class="o">(</span><span class="s1">'Member'</span>, <span class="nv">$null</span>, <span class="s1">'Definition'</span><span class="o">)</span>.
AddHeader<span class="o">(</span><span class="s1">'Left'</span>, 3, <span class="s1">'#'</span><span class="o">)</span>.
AddHeader<span class="o">(</span><span class="s1">'Left'</span>, 30, <span class="s1">'Type'</span><span class="o">)</span>.
AddHeader<span class="o">(</span><span class="s1">'Left'</span>, 20, <span class="s1">'Name'</span><span class="o">)</span>.
AddHeader<span class="o">(</span><span class="s1">'Center'</span>, 2, <span class="s1">'In'</span><span class="o">)</span>.
AddHeader<span class="o">(</span><span class="s1">'Center'</span>, 3, <span class="s1">'Out'</span><span class="o">)</span>.
AddHeader<span class="o">(</span><span class="s1">'Center'</span>, 3, <span class="s1">'Opt'</span><span class="o">)</span>.
StartRowDefinition<span class="o">(</span><span class="nv">$false</span><span class="o">)</span>.
AddPropertyColumn<span class="o">(</span><span class="s1">'Position'</span><span class="o">)</span>.
AddScriptBlockColumn<span class="o">(</span><span class="s1">'$_.ParameterType.Name'</span><span class="o">)</span>.
AddPropertyColumn<span class="o">(</span><span class="s1">'Name'</span><span class="o">)</span>.
AddScriptBlockColumn<span class="o">(</span><span class="s1">'if ($_.IsIn) { ''X'' }'</span><span class="o">)</span>.
AddScriptBlockColumn<span class="o">(</span><span class="s1">'if ($_.IsOut) { ''X'' }'</span><span class="o">)</span>.
AddScriptBlockColumn<span class="o">(</span><span class="s1">'if ($_.IsOptional) { ''X'' }'</span><span class="o">)</span>.
EndRowDefinition<span class="o">()</span>.
EndTable<span class="o">()</span>
</code></pre>
</div>
<p>If you’ve ever dug into reflection you know most of it is completely unformatted. For example, here
is what the <code class="highlighter-rouge">TableControl.Create()</code> method looks like.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="c1"># Before (this is just *one* parameter)</span>
<span class="nb">PS </span>C:<span class="se">\></span> <span class="o">[</span>System.Management.Automation.TableControl].GetMethod<span class="o">(</span><span class="s1">'Create'</span><span class="o">)</span>.GetParameters<span class="o">()</span>
ParameterType : System.Boolean
Name : outOfBand
HasDefaultValue : True
DefaultValue : False
RawDefaultValue : False
MetadataToken : 134234830
Position : 0
Attributes : Optional, HasDefault
Member : System.Management.Automation.TableControlBuilder Create<span class="o">(</span>Boolean, Boolean, Boolean<span class="o">)</span>
IsIn : False
IsOut : False
IsLcid : False
IsRetval : False
IsOptional : True
CustomAttributes : <span class="o">{[</span>System.Runtime.InteropServices.OptionalAttribute<span class="o">()]}</span>
<span class="c1"># After</span>
<span class="nb">PS </span>C:<span class="se">\></span> <span class="o">[</span>System.Management.Automation.TableControl].GetMethod<span class="o">(</span><span class="s1">'Create'</span><span class="o">)</span>.GetParameters<span class="o">()</span>
Definition: System.Management.Automation.TableControlBuilder Create<span class="o">(</span>Boolean, Boolean, Boolean<span class="o">)</span>
<span class="c1"># Type Name In Out Opt</span>
- ---- ---- -- --- ---
0 Boolean outOfBand X
1 Boolean autoSize X
2 Boolean hideTableHeaders X
</code></pre>
</div>
<h2 id="actually-loading-it">Actually loading it</h2>
<p>So you ran my example and all it did was return an object. Now what?</p>
<p>Well, we need to wrap it in an object that tells the formatter what types to target and what to name
our view. Combine this with the <code class="highlighter-rouge">Export-FormatData</code> and <code class="highlighter-rouge">Update-FormatData</code> cmdlets to load it into
the session.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="k">using </span>namespace System.Management.Automation
<span class="o">[</span>ExtendedTypeDefinition]::new<span class="o">(</span>
<span class="s1">'System.Reflection.ParameterInfo'</span>,
<span class="o">[</span>FormatViewDefinition]::new<span class="o">(</span>
<span class="s1">'MyParameterView'</span>,
<span class="o">[</span>TableControl]::Create<span class="o">()</span>.
GroupByProperty<span class="o">(</span><span class="s1">'Member'</span>, <span class="nv">$null</span>, <span class="s1">'Definition'</span><span class="o">)</span>.
AddHeader<span class="o">(</span><span class="s1">'Left'</span>, 3, <span class="s1">'#'</span><span class="o">)</span>.
AddHeader<span class="o">(</span><span class="s1">'Left'</span>, 30, <span class="s1">'Type'</span><span class="o">)</span>.
AddHeader<span class="o">(</span><span class="s1">'Left'</span>, 20, <span class="s1">'Name'</span><span class="o">)</span>.
AddHeader<span class="o">(</span><span class="s1">'Center'</span>, 2, <span class="s1">'In'</span><span class="o">)</span>.
AddHeader<span class="o">(</span><span class="s1">'Center'</span>, 3, <span class="s1">'Out'</span><span class="o">)</span>.
AddHeader<span class="o">(</span><span class="s1">'Center'</span>, 3, <span class="s1">'Opt'</span><span class="o">)</span>.
StartRowDefinition<span class="o">(</span><span class="nv">$false</span><span class="o">)</span>.
AddPropertyColumn<span class="o">(</span><span class="s1">'Position'</span><span class="o">)</span>.
AddScriptBlockColumn<span class="o">(</span><span class="s1">'$_.ParameterType.Name'</span><span class="o">)</span>.
AddPropertyColumn<span class="o">(</span><span class="s1">'Name'</span><span class="o">)</span>.
AddScriptBlockColumn<span class="o">(</span><span class="s1">'if ($_.IsIn) { ''X'' }'</span><span class="o">)</span>.
AddScriptBlockColumn<span class="o">(</span><span class="s1">'if ($_.IsOut) { ''X'' }'</span><span class="o">)</span>.
AddScriptBlockColumn<span class="o">(</span><span class="s1">'if ($_.IsOptional) { ''X'' }'</span><span class="o">)</span>.
EndRowDefinition<span class="o">()</span>.
EndTable<span class="o">()</span>
<span class="o">)</span> -as <span class="o">[</span>List[FormatViewDefinition]]
<span class="o">)</span> | <span class="k">ForEach</span>-Object <span class="o">{</span>
Export-FormatData -Path <span class="s2">".</span><span class="se">\$</span><span class="s2">(</span><span class="nv">$PSItem</span><span class="s2">.TypeName).ps1xml"</span> <span class="sb">`</span>
-InputObject <span class="nv">$PSItem</span> <span class="sb">`</span>
-IncludeScriptBlock <span class="sb">`</span>
-Force
<span class="c1"># Use -PrependPath for existing types, -AppendPath for custom ones.</span>
<span class="nb">Update-FormatData</span> -PrependPath <span class="s2">".</span><span class="se">\$</span><span class="s2">(</span><span class="nv">$PSItem</span><span class="s2">.TypeName).ps1xml"</span>
<span class="o">}</span>
</code></pre>
</div>
<p>I highly recommend exploring the objects with <code class="highlighter-rouge">Get-Member</code> or diving into the <a href="https://msdn.microsoft.com/en-us/library/system.management.automation.pscontrol(v=vs.85).aspx">MSDN documentation</a> for each class.</p>
<h2 id="getting-complex">Getting Complex</h2>
<p>So that was a small example of some pretty basic formatting. There are classes for all of the formatting types:
<code class="highlighter-rouge">ListControl</code>, <code class="highlighter-rouge">WideControl</code>, <code class="highlighter-rouge">CustomControl</code> and of course <code class="highlighter-rouge">TableControl</code>. The one you’ll probably
use the most for general formatting is <code class="highlighter-rouge">TableControl</code></p>
<p>But if you need <em>really</em> precise control over your output, you want <code class="highlighter-rouge">CustomControl</code>.</p>
<p>For example, I’ve been looking for a way to build flexible string expressions for generating code in editor commands.
I’ve been playing with the idea of using formatting for this because it’s really easy to build dynamic
statements, and you can easily customize it by adding your own view.</p>
<p>Here is a really early draft of some controls that take a <code class="highlighter-rouge">[type]</code> object and “implements” any abstract
or interface methods the type has.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code>
<span class="k">using </span>namespace System.Management.Automation
<span class="k">using </span>namespace System.Collections.Generic
<span class="nv">$parameterControl</span> <span class="o">=</span> <span class="o">[</span>CustomControl]::
Create<span class="o">()</span>.
StartEntry<span class="o">()</span>.
AddScriptBlockExpressionBinding<span class="o">(</span><span class="s1">'", "'</span>, 0, 0, <span class="s1">'$_.Position -ne 0'</span><span class="o">)</span>.
AddText<span class="o">(</span><span class="s1">'['</span><span class="o">)</span>.
AddPropertyExpressionBinding<span class="o">(</span><span class="s1">'ParameterType'</span><span class="o">)</span>.
AddText<span class="o">(</span><span class="s1">'] $'</span><span class="o">)</span>.
AddPropertyExpressionBinding<span class="o">(</span><span class="s1">'Name'</span><span class="o">)</span>.
EndEntry<span class="o">()</span>.
EndControl<span class="o">()</span>
<span class="nv">$methodControl</span> <span class="o">=</span> <span class="o">[</span>CustomControl]::
Create<span class="o">()</span>.
StartEntry<span class="o">()</span>.
AddNewline<span class="o">()</span>.
AddText<span class="o">(</span><span class="s1">'['</span><span class="o">)</span>.
AddPropertyExpressionBinding<span class="o">(</span><span class="s1">'ReturnType'</span><span class="o">)</span>.
AddText<span class="o">(</span><span class="s1">'] '</span><span class="o">)</span>.
AddPropertyExpressionBinding<span class="o">(</span><span class="s1">'Name'</span><span class="o">)</span>.
AddText<span class="o">(</span><span class="s1">' ('</span><span class="o">)</span>.
AddScriptBlockExpressionBinding<span class="o">(</span>
<span class="cm"><# scriptBlock: #></span> <span class="s1">'$_.GetParameters()'</span>,
<span class="cm"><# enumerateCollection: #></span> 1,
<span class="cm"><# selectedByType: #></span> 0,
<span class="cm"><# selectedByScript: #></span> <span class="s1">'$_.GetParameters().Count'</span>,
<span class="cm"><# customControl: #></span> <span class="nv">$parameterControl</span>
<span class="o">)</span>.
AddText<span class="o">(</span><span class="s1">') {'</span><span class="o">)</span>.
AddNewline<span class="o">()</span>.
StartFrame<span class="o">(</span>4<span class="o">)</span>.
AddText<span class="o">(</span><span class="s1">'throw [NotImplementedException]::new()'</span><span class="o">)</span>.
AddNewline<span class="o">()</span>.
EndFrame<span class="o">()</span>.
AddText<span class="o">(</span><span class="s1">'}'</span><span class="o">)</span>.
AddNewline<span class="o">()</span>.
EndEntry<span class="o">()</span>.
EndControl<span class="o">()</span>
<span class="nv">$classControl</span> <span class="o">=</span> <span class="o">[</span>CustomControl]::
Create<span class="o">()</span>.
StartEntry<span class="o">()</span>.
AddText<span class="o">(</span><span class="s1">'class MyClass : '</span><span class="o">)</span>.
AddScriptBlockExpressionBinding<span class="o">(</span><span class="s1">'$_'</span><span class="o">)</span>.
AddText<span class="o">(</span><span class="s1">' {'</span><span class="o">)</span>.
AddNewline<span class="o">()</span>.
StartFrame<span class="o">(</span>4<span class="o">)</span>.
AddScriptBlockExpressionBinding<span class="o">(</span>
<span class="cm"><# scriptBlock: #></span> <span class="s1">'
if ($_.IsAbstract) {
$return = $_.DeclaredMethods.
Where{ $_.IsAbstract }
} elseif ($_.IsInterface) {
$return = $_.DeclaredMethods
}
$return
'</span>,
<span class="cm"><# enumerateCollection: #></span> <span class="nv">$true</span>,
<span class="cm"><# selectedByType: #></span> <span class="nv">$null</span>,
<span class="cm"><# selectedByScript: #></span> <span class="s1">'
$_.IsInterface -or
($_.IsAbstract -and
$_.DeclaredMethods.Where{ $_.IsAbstract })
'</span>,
<span class="cm"><# customControl: #></span> <span class="nv">$methodControl</span>
<span class="o">)</span>.
EndFrame<span class="o">()</span>.
AddText<span class="o">(</span><span class="s1">'}'</span><span class="o">)</span>.
EndEntry<span class="o">()</span>.
EndControl<span class="o">()</span>
<span class="nv">$formats</span> <span class="o">=</span> @<span class="o">(</span>
<span class="o">[</span>ExtendedTypeDefinition]::new<span class="o">(</span>
<span class="s1">'System.Reflection.RuntimeParameterInfo'</span>,
<span class="o">[</span>FormatViewDefinition]::new<span class="o">(</span>
<span class="s1">'ParameterView'</span>,
<span class="nv">$parameterControl</span>
<span class="o">)</span> -as <span class="o">[</span>List[FormatViewDefinition]]
<span class="o">)</span>
<span class="o">[</span>ExtendedTypeDefinition]::new<span class="o">(</span>
<span class="s1">'System.Reflection.RuntimeMethodInfo'</span>,
<span class="o">[</span>FormatViewDefinition]::new<span class="o">(</span>
<span class="s1">'MethodView'</span>,
<span class="nv">$methodControl</span>
<span class="o">)</span> -as <span class="o">[</span>List[FormatViewDefinition]]
<span class="o">)</span>
<span class="o">[</span>ExtendedTypeDefinition]::new<span class="o">(</span>
<span class="s1">'System.RuntimeType'</span>,
<span class="o">[</span>FormatViewDefinition]::new<span class="o">(</span>
<span class="s1">'TypeView'</span>,
<span class="nv">$classControl</span>
<span class="o">)</span> -as <span class="o">[</span>List[FormatViewDefinition]]
<span class="o">)</span>
<span class="o">)</span>
</code></pre>
</div>
<p>And here it is in action.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nb">PS </span>C:<span class="se">\></span> <span class="o">[</span>System.Collections.IDictionary]
<span class="c1"># This won't actually load because it missed a method, but you get the idea.</span>
<span class="k">class </span>MyClass : System.Collections.IDictionary <span class="o">{</span>
<span class="o">[</span>System.Object] get_Item <span class="o">([</span>System.Object] <span class="nv">$key</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="o">[</span>NotImplementedException]::new<span class="o">()</span>
<span class="o">}</span>
<span class="o">[</span>System.Void] set_Item <span class="o">([</span>System.Object] <span class="nv">$key</span>, <span class="o">[</span>System.Object]
<span class="nv">$value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="o">[</span>NotImplementedException]::new<span class="o">()</span>
<span class="o">}</span>
<span class="o">[</span>System.Collections.ICollection] get_Keys <span class="o">()</span> <span class="o">{</span>
<span class="k">throw</span> <span class="o">[</span>NotImplementedException]::new<span class="o">()</span>
<span class="o">}</span>
<span class="o">[</span>System.Collections.ICollection] get_Values <span class="o">()</span> <span class="o">{</span>
<span class="k">throw</span> <span class="o">[</span>NotImplementedException]::new<span class="o">()</span>
<span class="o">}</span>
<span class="o">[</span>System.Boolean] Contains <span class="o">([</span>System.Object] <span class="nv">$key</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="o">[</span>NotImplementedException]::new<span class="o">()</span>
<span class="o">}</span>
<span class="o">[</span>System.Void] Add <span class="o">([</span>System.Object] <span class="nv">$key</span>, <span class="o">[</span>System.Object] <span class="nv">$value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="o">[</span>NotImplementedException]::new<span class="o">()</span>
<span class="o">}</span>
<span class="o">[</span>System.Void] <span class="nb">Clear</span> <span class="o">()</span> <span class="o">{</span>
<span class="k">throw</span> <span class="o">[</span>NotImplementedException]::new<span class="o">()</span>
<span class="o">}</span>
<span class="o">[</span>System.Boolean] get_IsReadOnly <span class="o">()</span> <span class="o">{</span>
<span class="k">throw</span> <span class="o">[</span>NotImplementedException]::new<span class="o">()</span>
<span class="o">}</span>
<span class="o">[</span>System.Boolean] get_IsFixedSize <span class="o">()</span> <span class="o">{</span>
<span class="k">throw</span> <span class="o">[</span>NotImplementedException]::new<span class="o">()</span>
<span class="o">}</span>
<span class="o">[</span>System.Collections.IDictionaryEnumerator] GetEnumerator <span class="o">()</span> <span class="o">{</span>
<span class="k">throw</span> <span class="o">[</span>NotImplementedException]::new<span class="o">()</span>
<span class="o">}</span>
<span class="o">[</span>System.Void] Remove <span class="o">([</span>System.Object] <span class="nv">$key</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="o">[</span>NotImplementedException]::new<span class="o">()</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre>
</div>
<h2 id="final-thoughts">Final thoughts</h2>
<p>There <strong>is</strong> a way to load this directly without writing it to XML, but it requires a <em>huge</em> amount
of reflection and isn’t really consistant. If anyone is looking for a project, a domain specific
language that does all this for you would be <em>really</em> cool.</p>
<p>Also, if you’re looking for more examples, check out the <a href="https://github.com/PowerShell/PowerShell/tree/master/src/System.Management.Automation/commands/utility/FormatAndOutput/common/DefaultFormatters">DefaultFormatters</a> folder in the PowerShell repo.
It’s all in C#, but it should be pretty easy to translate.</p>
Cmdlet Creation with PowerShell2017-04-13T00:00:00+00:00http://seeminglyscience.github.io/powershell/2017/04/13/cmdlet-creation-with-powershell
<p>I was looking through the <a href="https://github.com/PowerShell/PowerShell-Tests/tree/master">PowerShell-Tests</a> repo and I saw they were creating Cmdlets inline for some tests. They were using C# and Add-Type which got me wondering.</p>
<p>Can you create one using pure PowerShell? Yeah, you can. With a little extra handling.</p>
<h2 id="building-the-cmdlet">Building the Cmdlet</h2>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="k">using </span>namespace System.Management.Automation
<span class="k">using </span>namespace System.Reflection
<span class="c1"># Basic do nothing Cmdlet.</span>
<span class="o">[</span>Cmdlet<span class="o">([</span>VerbsDiagnostic]::Test, <span class="s1">'Cmdlet'</span><span class="o">)]</span>
<span class="k">class </span>TestCmdletCommand : PSCmdlet <span class="o">{</span>
<span class="o">[</span>Parameter<span class="o">(</span>ValueFromPipeline<span class="o">)]</span>
<span class="o">[</span><span class="kt">object</span><span class="o">]</span>
<span class="nv">$InputObject</span>;
<span class="o">[</span>void] ProcessRecord <span class="o">()</span> <span class="o">{</span>
<span class="nv">$this</span>.WriteObject<span class="o">(</span><span class="nv">$this</span>.InputObject<span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre>
</div>
<p>Just a basic cmdlet that passes anything it gets from the pipeline. But right now, it’s just a class.
Now we need to tell PowerShell it’s a cmdlet. This is where we have to stretch a little.</p>
<!--more-->
<p>First, we need to create a session state entry.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nv">$cmdletEntry</span> <span class="o">=</span> <span class="o">[</span>Runspaces.SessionStateCmdletEntry]::new<span class="o">(</span>
<span class="cm"><# name: #></span> <span class="s1">'Test-Cmdlet'</span>,
<span class="cm"><# implementingType: #></span> <span class="o">[</span>TestCmdletCommand],
<span class="cm"><# helpFileName: #></span> <span class="nv">$null</span>
<span class="o">)</span>
</code></pre>
</div>
<p>Then we need to get the internal session state. This is the “private” version of the normally accessible
<code class="highlighter-rouge">$ExecutionContext.SessionState</code>.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nv">$internal</span> <span class="o">=</span> <span class="nv">$ExecutionContext</span>.SessionState.GetType<span class="o">()</span>.
GetProperty<span class="o">(</span><span class="s1">'Internal'</span>, <span class="o">[</span>BindingFlags]<span class="s1">'Instance, NonPublic'</span><span class="o">)</span>.
GetValue<span class="o">(</span><span class="nv">$ExecutionContext</span>.SessionState<span class="o">)</span>
</code></pre>
</div>
<p>Now we can load the session state entry into our current session.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nv">$internal</span>.GetType<span class="o">()</span>.InvokeMember<span class="o">(</span>
<span class="cm"><# name: #></span> <span class="s1">'AddSessionStateEntry'</span>,
<span class="cm"><# invokeAttr: #></span> <span class="o">[</span>BindingFlags]<span class="s1">'InvokeMethod, Instance, NonPublic'</span>,
<span class="cm"><# binder: #></span> <span class="nv">$null</span>,
<span class="cm"><# target: #></span> <span class="nv">$internal</span>,
<span class="cm"><# args: #></span> @<span class="o">(</span>
<span class="cm"><# entry: #></span> <span class="nv">$cmdletEntry</span>
<span class="cm"><# local: #></span> <span class="nv">$true</span>
<span class="o">)</span>
<span class="o">)</span>
</code></pre>
</div>
<p>That still won’t actually load the cmdlet. But if we wrap all that in a module (dump it in a .psm1 file)
and import it, we have ourselves a working cmdlet.</p>
<h2 id="psclasscmdletpsm1">PSClassCmdlet.psm1</h2>
<script src="https://gist.github.com/SeeminglyScience/533140625f67f2f46535e652c8cd6da2.js"></script>
<h2 id="trying-it-out">Trying it out</h2>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nb">PS</span>> Import-Module .<span class="se">\P</span>SClassCmdlet.psm1
<span class="nb">PS</span>> Get-Module PSClassCmdlet.psm1
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 0.0 PSClassCmdlet Test-Cmdlet
<span class="nb">PS</span>> <span class="nb">Get-Command </span>Test-Cmdlet
CommandType Name Version Source
----------- ---- ------- ------
Cmdlet Test-Cmdlet 0.0 PSClassCmdlet
<span class="nb">PS</span>> 0..3 | Test-Cmdlet
0
1
2
3
</code></pre>
</div>
<h2 id="why-use-this-over-functions">Why use this over functions</h2>
<p>That’s a good question. I haven’t actually tried using it past this example, but here are some thoughts.</p>
<h3 id="using-the-class-structure">Using the class structure</h3>
<p>There doesn’t seem to be a whole lot of adoption of classes just yet. But if you’re a fan this could
be a nice alternative to traditional PowerShell functions.</p>
<h3 id="inheritance">Inheritance</h3>
<p>Inheritance is the main value I see. Lets say you are working on a module where every command shares
some parameters. You could set up a base cmdlet and every child cmdlet will share those parameters.
You could also inherit existing cmdlets. If that’s something you’re interested in you can run this
snippet to show you what existing cmdlets can be inherited.</p>
<div class="language-powershell highlighter-rouge"><pre class="highlight"><code><span class="nv">$assemblies</span> <span class="o">=</span> <span class="o">[</span>AppDomain]::CurrentDomain.GetAssemblies<span class="o">()</span>
<span class="nv">$assemblies</span>.GetTypes<span class="o">()</span>.Where<span class="o">{</span>
<span class="o">[</span>System.Management.Automation.Cmdlet].IsAssignableFrom<span class="o">(</span><span class="nv">$_</span><span class="o">)</span> -and
-not <span class="nv">$_</span>.IsSealed
<span class="o">}</span>
</code></pre>
</div>
<h2 id="word-of-warning">Word of warning</h2>
<p>Unless there is a much easier way to load these than I found, it’s pretty clear this isn’t exactly
<em>supported</em>. If you decide to give this a go, keep in mind that some things may act unpredicably.
If I happen to find any gotchas I’ll update this post.</p>