다음을 통해 공유


Adding Type Accelerators in the PowerShell 5.0 April 2015 Preview

Update:  This behavior has been corrected in the RTM version.

I had been experimenting with and using the PowerShell 5.0 Preview from April 2015 and generally enjoying the experience.  I had even forgotten that I was using the preview during day-to-day usage.  The preview, in my mind, is exceptionally stable, and I can't wait to see the RTM version.

Therefore, imagine my frustration when my Add-TypeAccelerators script ceased to function.  This script was designed to add type accelerators to the PowerShell session.  For example, if you use System.Collections.ArrayList quite a bit in a script, you might like to have a shortened version like [AList] to use instead.  In order to achieve this, you have to be able to add your type accelerator reference to the dictionary located under System.Management.Automation.TypeAccelerators.  This dictionary was public and accessible in PowerShell 2.0, and you could just call its static Add method:

1.PS C:\> [System.Management.Automation.TypeAccelerators]::Add("AList", [System.Collections.ArrayList])

In PowerShell 3.0/4.0 this was made private, so according to this Hey, Scripting Guy! Blog article, the way to achieve this was to use the PowerShell assembly to retrieve the type, store it in a variable, and then call the Add method:

1.PS C:\> $accel = [PowerShell].Assembly.GetType("System.Management.Automation.TypeAccelerators")
2.PS C:\> $accel::Add("AList", [System.Collections.ArrayList])

This was successful and can be seen when accessing the TypeAccelerators static Get property:

01.PS C:\> $accel::Get
02. 
03.Key                   Value                                                            
04.---                   -----                                                            
05.Alias                 System.Management.Automation.Alias...                                           
06..
07..
08..                   
09.psvariableproperty    System.Management.Automation.PSVar...                 
10.AList                 System.Collections.ArrayList

It's there at the bottom and we are free to use it like any other type accelerator...until the 5.0 April 2015 Preview when calling the TypeAcclerators static Add method still resulted in a successful addition to the dictionary, but even though it is listed, it is not usable:

1.PS C:\> [AList]
2.Unable to find type [AList].
3.At line:1 char:1
4.+ [AList]
5.+ ~~~~~~~
6.    + CategoryInfo          : InvalidOperation: (AList:TypeName) [], RuntimeException
7.    + FullyQualifiedErrorId : TypeNotFound

Now, we'll have to do some digging.  Checking the reference source on MSDN only provides us with the static Add and Remove methods, and the static Get property.  What about the underlying private fields of the TypeAccelerators class?  We can find those:

1.PS C:\> $accel.GetFields([System.Reflection.BindingFlags]"static, nonpublic")

The output from that is quite a bit, but the important matter is there are three FieldInfo objects returned with the names builtinTypeAccelerators, userTypeAccelerators, and allTypeAccelerators.  By the way, the binding flags are there because, by default, GetFields and GetField only return public non-static fields.  Let's see what's in the userTypeAccelerators:

1.PS C:\> $accel.GetField("userTypeAccelerators", [System.Reflection.BindingFlags]"Static,NonPublic").GetValue($accel)
2. 
3.Key   Value                       
4.---   -----                       
5.AList System.Collections.ArrayList

We now know that anything we add to the dictionary is going to be ultimately stored in the userTypeAccelerators field, all the built-in accelerators are ultimately stored in the builtinTypeAccelerators field, and the combination of the two is stored in the allTypeAccelerators field.  

Apparently, it is the builtinTypeAccelerators field that the PowerShell engine is using in the preview to reference its valid type accelerators.  To fix our problem, we just need to get our user defined accelerators into that field without overwriting the ones already there, so let's just replace builtinTypeAccelerators with all the entries in the Get static property:

1.PS C:\> $builtinField = $accel.GetField("builtinTypeAccelerators", [System.Reflection.BindingFlags]"Static,NonPublic")
2.PS C:\> $builtinField.SetValue($builtinField, $accel::Get)

And now, let's try our user defined type again:

1.PS C:\> [AList]
2. 
3.IsPublic IsSerial Name                                                                                        
4.-------- -------- ----                                  
5.True     True     ArrayList

Success!  It's the little things.

Disclaimer:  I do not know if this will change and become even more difficult in the RTM, or whether it will be reverted back to the behavior of PowerShell 3.0/4.0.  For now, as of the PowerShell 5.0 April 2015 preview, this method works, which I'll condense down into the four lines necessary once all the research and digging are stripped away:

1.PS C:\> $accel = [PowerShell].Assembly.GetType("System.Management.Automation.TypeAccelerators")
2.PS C:\> $accel::Add("AList",[System.Collections.ArrayList])
3.PS C:\> $builtinField = $accel.GetField("builtinTypeAccelerators", [System.Reflection.BindingFlags]"Static,NonPublic")
4.PS C:\> $builtinField.SetValue($builtinField, $accel::Get)

I hope this helps.