R – How to add a property to a function object on the PowerShell PSDrive “function:”

powershellproperties

I've attempted using the $function:foo value and get-item function:foo. All attempts succeed in modifying the temporary function object, but the additional property is missing when reassigned to the stored function (either through $function:foo = … or set-item function:foo …).

Here are the results of my attempts (all fail):

Setup

$=>function foo { "foo" }
$=>$f = $function:foo
$=>$f = $f | add-member noteproperty bar BARvalue -pass
$=>$f | gm b*

   TypeName: System.Management.Automation.ScriptBlock

Name                 MemberType   Definition
----                 ----------   ----------
bar                  NoteProperty System.String bar=BARvalue

#1

$=>set-item function:f  $f -force
$=>$function:foo | gm b*
>

#2

$=>$function:f = $f
$=>$function:foo | gm b*
>

#3

$=>$f = get-item function:foo
$=>$f | gm

   TypeName: System.Management.Automation.FunctionInfo

Name                MemberType   Definition
----                ----------   ----------
Equals              Method       System.Boolean Equals(Object obj)
GetHashCode         Method       System.Int32 GetHashCode()
GetType             Method       System.Type GetType()
ToString            Method       System.String ToString()
PSDrive             NoteProperty System.Management.Automation.PSDriveInfo PSDrive=Function
PSIsContainer       NoteProperty System.Boolean PSIsContainer=False
PSPath              NoteProperty System.String PSPath=Microsoft.PowerShell.Core\Function::foo
PSProvider          NoteProperty System.Management.Automation.ProviderInfo PSProvider=Microsoft....
CmdletBinding       Property     System.Boolean CmdletBinding {get;}
CommandType         Property     System.Management.Automation.CommandTypes CommandType {get;}
DefaultParameterSet Property     System.String DefaultParameterSet {get;}
Definition          Property     System.String Definition {get;}
Description         Property     System.String Description {get;set;}
Module              Property     System.Management.Automation.PSModuleInfo Module {get;}
ModuleName          Property     System.String ModuleName {get;}
Name                Property     System.String Name {get;}
Options             Property     System.Management.Automation.ScopedItemOptions Options {get;set;}
Parameters          Property     System.Collections.Generic.Dictionary`2[[System.String, mscorli...
ParameterSets       Property     System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Man...
ScriptBlock         Property     System.Management.Automation.ScriptBlock ScriptBlock {get;}
Visibility          Property     System.Management.Automation.SessionStateEntryVisibility Visibi...

$=>$f = $f | add-member noteproperty bar barValue -pass
$=>$f | gm b*

   TypeName: System.Management.Automation.FunctionInfo

Name                MemberType   Definition
----                ----------   ----------
bar                 NoteProperty System.String bar=barValue

$=>set-item function:foo $f
$=>$function:foo | gm b*
>

Not sure what I'm doing wrong. It seems like the properties are being stripped out when reassigned. Is that correct? the defined behavior? I haven't seen any documentation saying that FunctionInfo objects or ScriptBlocks are treated unusually. Is this some esoteric corner of the language?

Best Answer

My first thought is when you are attaching this property to an object, you are attaching it to a specific instance of that object. When your variable losses the reference to that object, any knowledge of that new property is lost.

My guess is the next time you get that item, you are creating a new FunctionInfo object with foo's properties (as stored in the function provider).

When you call Get-Item or Get-ChildItem, it returns object references to the .NET types that represent the underlying items. Those items do not exist in memory indefinitely (imagine a FileInfo object for every file on every local drive and every mapped drive living in memory.. ouch). Since PowerShell is creating a new instance every time you call Get-Item, you are getting the basic FunctionInfo object.

If you want to add a property to all items of a particular type, you can with PowerShell's extensible type system. You can create a custom .ps1xml file and load that into a PowerShell session that can add a property to every instance of a type. Some great examples on the PowerShell Team Blog are here -> Hate Add-Member and Leveraging the PowerShell Type Extensions to Get Documentation.

EDIT (addressing comment): I understand what you are trying to do, but the failure is due to the new property being "grafted" on to a PSObject wrapper that allows for the on-the-fly addition of properties. The new property is never really part of that FunctionInfo object that you are retrieving from the PSDrive.

In addition, the function provider (the function: psdrive), does not have a mechanism for storing that additional property. It knows and works with FunctionInfo objects. When you ask for an item from the Function: PSDrive, the Function provider returns a FunctionInfo object. When you save a function to the Function: PSDrive, the provider can only store values for properties it knows how to handle. One could write a custom provider that would handle new properties from the PSObject wrapper, but that is not part of the default functionality in this provider.

EDIT #2: Ok, this has been bugging me. I blogged about a workaround.