Welcome PowerShell User! This recipe is just one of the hundreds of useful resources contained in the PowerShell Cookbook.

If you own the book already, login here to get free, online, searchable access to the entire book's content.

If not, the Windows PowerShell Cookbook is available at Amazon, or any of your other favourite book retailers. If you want to see what the PowerShell Cookbook has to offer, enjoy this free 90 page e-book sample: "The Windows PowerShell Interactive Shell".

3.16 Add Custom Methods and Properties to Types

Problem

You want to add your own custom properties or methods to all objects of a certain type.

Solution

Use the Update-TypeData cmdlet to add custom members to all objects of a type.

Update-TypeData -TypeName AddressRecord `
    -MemberType AliasProperty -Membername Cell -Value Phone

Alternatively, use custom type extension files.

Discussion

Although the Add-Member cmdlet is extremely useful in helping you add custom members to individual objects, it requires that you add the members to each object that you want to interact with. It does not let you automatically add them to all objects of that type. For that purpose, PowerShell supports another mechanism—custom type extensions.

The simplest and most common way to add members to all instances of a type is through the Update-TypeData cmdlet. This cmdlet supports aliases, notes, script methods, and more:

$r = [PSCustomObject] @{
    Name = "Lee";
    Phone = "555-1212";
    SSN = "123-12-1212"
}
$r.PSTypeNames.Add("AddressRecord")
Update-TypeData -TypeName AddressRecord `
    -MemberType AliasProperty -Membername Cell -Value Phone

Custom type extensions let you easily add your own features to any type exposed by the system. If you write code (for example, a script or function) that primarily interacts with a single type of object, then that code might be better suited as an extension to the type instead.

For example, imagine a script that returns the free disk space on a given drive. That might be helpful as a script, but instead you might find it easier to make PowerShell’s PSDrive objects themselves tell you how much free space they have left.

In addition to the Update-TypeData approach, PowerShell supports type extensions through XML-based type extension files. Since type extension files are XML files, make sure that your customizations properly encode the characters that have special meaning in XML files, such as <, >, and &.

For more information about the features supported by these formatting XML files, type Get-Help about_format.ps1xml.

Getting started

If you haven’t done so already, the first step in creating a type extension file is to create an empty one. The best location for this is probably in the same directory as your custom profile, with the filename Types.Custom.ps1xml, as shown in Example 3-11.

Example 3-11. Sample Types.Custom.ps1xml file
<?xml version="1.0" encoding="utf-8" ?>
<Types>
</Types>

Next, add a few lines to your PowerShell profile so that PowerShell loads your type extensions during startup:

$typeFile = (Join-Path (Split-Path $profile) "Types.Custom.ps1xml")
Update-TypeData -PrependPath $typeFile

By default, PowerShell loads several type extensions from its internal configuration stores. The Update-TypeData cmdlet tells PowerShell to also look in your Types.Custom.ps1xml file for extensions. The -PrependPath parameter makes PowerShell favor your extensions over the built-in ones in case of conflict.

Once you have a custom types file to work with, adding functionality becomes relatively straightforward. As a theme, these examples do exactly what we alluded to earlier: add functionality to PowerShell’s PSDrive type.

Note

PowerShell does this automatically. Type Get-PSDrive to see the result.

To support this, you need to extend your custom types file so that it defines additions to the System.Management.Automation.PSDriveInfo type, shown in Example 3-12. System.Management.Automation.PSDriveInfo is the type that the Get-PSDrive cmdlet generates.

Example 3-12. A template for changes to a custom types file
<?xml version="1.0" encoding="utf-8" ?>
<Types>
  <Type>
    <Name>System.Management.Automation.PSDriveInfo</Name>
    <Members>
        add members such as <ScriptProperty> here
    <Members>
  </Type>
</Types>

Add a ScriptProperty

A ScriptProperty lets you add properties (that get and set information) to types, using PowerShell script as the extension language. It consists of three child elements: the Name of the property, the getter of the property (via the GetScriptBlock child), and the setter of the property (via the SetScriptBlock child).

In both the GetScriptBlock and SetScriptBlock sections, the $this variable refers to the current object being extended. In the SetScriptBlock section, the $args[0] variable represents the value that the user supplied as the righthand side of the assignment.

Example 3-13 adds an AvailableFreeSpace ScriptProperty to PSDriveInfo, and should be placed within the members section of the template given in Example 3-12. When you access the property, it returns the amount of free space remaining on the drive. When you set the property, it outputs what changes you must make to obtain that amount of free space.

Example 3-13. A ScriptProperty for the PSDriveInfo type
<ScriptProperty>
  <Name>AvailableFreeSpace</Name>
  <GetScriptBlock>
    ## Ensure that this is a FileSystem drive
    if($this.Provider.ImplementingType -eq
       [Microsoft.PowerShell.Commands.FileSystemProvider])
    {
       ## Also ensure that it is a local drive
       $driveRoot = $this.Root
       $fileZone = [System.Security.Policy.Zone]::CreateFromUrl(
                 $driveRoot).SecurityZone
       if($fileZone -eq "MyComputer")
       {
          $drive = New-Object System.IO.DriveInfo $driveRoot
          $drive.AvailableFreeSpace
       }
    }
  </GetScriptBlock>
  <SetScriptBlock>
   ## Get the available free space
   $availableFreeSpace = $this.AvailableFreeSpace

   ## Find out the difference between what is available, and what they
   ## asked for.
   $spaceDifference = (([long] $args[0]) - $availableFreeSpace) / 1MB

   ## If they want more free space than they have, give that message
   if($spaceDifference -gt 0)
   {
       $message = "To obtain $args bytes of free space, " +
          " free $spaceDifference megabytes."
       Write-Host $message
   }
   ## If they want less free space than they have, give that message
   else
   {
       $spaceDifference = $spaceDifference * -1
       $message = "To obtain $args bytes of free space, " +
           " use up $spaceDifference more megabytes."
        Write-Host $message
   }
  </SetScriptBlock>
</ScriptProperty>

Add an AliasProperty

An AliasProperty gives an alternative name (alias) for a property. The referenced property doesn’t need to exist when PowerShell processes your type extension file, since you (or another script) might later add the property through mechanisms such as the Add-Member cmdlet.

Example 3-14 adds a Free AliasProperty to PSDriveInfo, and it should also be placed within the members section of the template given in Example 3-12. When you access the property, it returns the value of the AvailableFreeSpace property. When you set the property, it sets the value of the AvailableFreeSpace property.

Example 3-14. An AliasProperty for the PSDriveInfo type
<AliasProperty>
  <Name>Free</Name>
  <ReferencedMemberName>AvailableFreeSpace</ReferencedMemberName>
</AliasProperty>

Add a ScriptMethod

A ScriptMethod lets you define an action on an object, using PowerShell script as the extension language. It consists of two child elements: the Name of the property and the Script.

In the script element, the $this variable refers to the current object you are extending. Like a standalone script, the $args variable represents the arguments to the method. Unlike standalone scripts, ScriptMethods do not support the param statement for parameters.

Example 3-15 adds a Remove ScriptMethod to PSDriveInfo. Like the other additions, place these customizations within the members section of the template given in Example 3-12. When you call this method with no arguments, the method simulates removing the drive (through the -WhatIf option to Remove-PSDrive). If you call this method with $true as the first argument, it actually removes the drive from the PowerShell session.

Example 3-15. A ScriptMethod for the PSDriveInfo type
<ScriptMethod>
  <Name>Remove</Name>
  <Script>
    $force = [bool] $args[0]
    ## Remove the drive if they use $true as the first parameter
    if($force)
    {
       $this | Remove-PSDrive
    }
    ## Otherwise, simulate the drive removal
    else
    {
       $this | Remove-PSDrive -WhatIf
    }
  </Script>
</ScriptMethod>

Add other extension points

PowerShell supports several additional features in the types extension file, including CodeProperty, NoteProperty, CodeMethod, and MemberSet. Although not generally useful to end users, developers of PowerShell providers and cmdlets will find these features helpful. For more information about these additional features, see the PowerShell software developer’s kit (SDK) or the Microsoft documentation.