Chapter 3. Variables and Objects

Introduction

As touched on in Chapter 2, Pipelines, PowerShell makes life immensely easier by keeping information in its native form: objects. Users expend most of their effort in traditional shells just trying to resuscitate information that the shell converted from its native form to plain text. Tools have evolved that ease the burden of working with plain text, but that job is still significantly more difficult than it needs to be.

1 comment

  1. Kjartan Þór Kjartansson Posted 16 days and 5 hours ago

    I will review this chapter

Add a comment

Since PowerShell builds on Microsoft's .NET Framework, native information comes in the form of .NET objects—packages of information, and functionality closely related to that information.

Let's say that you want to get a list of running processes on your system. In other shells, your command (such as tlist.exe or /bin/ps) generates a plain-text report of the running processes on your system. To work with that output, you send it through a bevy of text processing tools—if you are lucky enough to have them available.

PowerShell's Get-Process cmdlet generates a list of the running processes on your system. In contrast to other shells, though, these are full-fidelity System.Diagnostics.Process objects straight out of the .NET Framework. The .NET Framework documentation describes them as objects that "… [provide] access to local and remote processes, and [enable] you to start and stop local system processes." With those objects in hand, PowerShell makes it trivial for you to access properties of objects (such as their process name or memory usage) and to access functionality on these objects (such as stopping them, starting them, or waiting for them to exit).

1 comment

  1. David "Makovec" Moravec Posted 17 days ago

    'System.Diagnostics. Process' should be 'System.Diagnostics.Process' - without a space.

Add a comment

Display the Properties of an Item As a List

Problem

You have an item (for example, an error record, directory item, or .NET object), and you want to display detailed information about that object in a list format.

Solution

To display detailed information about an item, pass that item to the Format-List cmdlet. For example, to display an error in list format, type the commands:

$currentError = $error[0]
$currentError | Format-List -Force

Discussion

Many commands by default display a summarized view of their output in a table format. For example, the Get-Process cmdlet:

1 comment

  1. David "Makovec" Moravec Posted 17 days ago

    Strictly speaking there are four formatting cmdlets. The fourth one is Format-Custom. If you will not mention it in the book it's still something to at least recommend for home study.

Add a comment

PS > Get-Process PowerShell

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    920      10    43808      48424   183     4.69   1928 powershell
    149       6    18228       8660   146     0.48   1940 powershell
    431      11    33308      19072   172            2816 powershell

In most cases, the output actually contains a great deal more information. You can use the Format-List cmdlet to view it:

PS > Get-Process PowerShell | Format-List *


__NounName                 : Process
Name                       : powershell
Handles                    : 443
VM                         : 192176128
WS                         : 52363264
PM                         : 47308800
NPM                        : 9996
Path                       : C:\WINDOWS\system32\WindowsPowerShell\v1.0\power
                             shell.exe
Company                    : Microsoft Corporation
CPU                        : 4.921875
FileVersion                : 6.0.6002.18139 (vistasp2_gdr_win7ip_winman(wmbla
                             ).090902-1426)
ProductVersion             : 6.0.6002.18139
Description                : Windows PowerShell
(...)

The Format-List cmdlet is one of the four PowerShell formatting cmdlets. These cmdlets include Format-Table, Format-List, Format-Wide, and Format-Custom. The Format-List cmdlet takes input and displays information about that input as a list.

By default, PowerShell takes the list of properties to display from the *.format.ps1xml files in PowerShell's installation directory. In many situations, you'll only get a small set of the properties:

PS > Get-Process PowerShell | Format-List

Id      : 2816
Handles : 431
CPU     :
Name    : powershell

Id      : 5244
Handles : 665
CPU     : 10.296875
Name    : powershell

To display all properties of the item, type Format-List *. If you type Format-List * but still do not get a list of the item's properties, then the item is defined in the *.format.ps1xml files, but does not define anything to be displayed for the list command. In that case, type Format-List -Force.

One common stumbling block in PowerShell's formatting cmdlets comes from putting them in the middle of a script or pipeline:

PS > Get-Process PowerShell | Format-List | Sort Name
out-lineoutput : The object of type "Microsoft.PowerShell.Commands.Internal.F
ormat.FormatEntryData" is not valid or not in the correct sequence. This is l
ikely caused by a user-specified "format-*" command which is conflicting with
 the default formatting.

Internally, PowerShell's formatting commands generate a new type of object: Microsoft.PowerShell.Commands.Internal.Format.*. When these objects make it to the end of the pipeline, PowerShell then automatically sends them to an output cmdlet: by default, Out-Default. These Out-* cmdlets assume that the objects arrive in a certain order, so doing anything with the output of the formatting commands causes an error the the output system.

To resolve this problem, try to avoid calling the formatting cmdlets in the middle of a script or pipeline. When you do this, the output of your script no longer lends itself to the object-based manipulation so synonymous with PowerShell. If you want to display formatted output anyway, send the output through the Out-String cmdlet:

Get-Process PowerShell | Format-List | Out-String -Stream

Object-manipulations commands will still not work (since the objects have been converted to strings), but at least the the script will not generate errors.

For more information about the Format-List cmdlet, type Get-Help Format-List.

Display the Properties of an Item As a Table

Problem

You have a set of items (for example, error records, directory items, or .NET objects), and you want to display summary information about them in a table format.

Solution

To display summary information about a set of items, pass those items to the Format-Table cmdlet. This is the default type of formatting for sets of items in PowerShell and provides several useful features.

To use PowerShell's default formatting, pipe the output of a cmdlet (such as the Get-Process cmdlet) to the Format-Table cmdlet:

Get-Process | Format-Table

To display specific properties (such as Name and WorkingSet), in the table formatting, supply those property names as parameters to the Format-Table cmdlet:

Get-Process | Format-Table Name,WS

To instruct PowerShell to format the table in the most readable manner, supply the -Auto flag to the Format-Table cmdlet. PowerShell defines "WS" as an alias of the WorkingSet property for processes:

Get-Process | Format-Table Name,WS -Auto

To define a custom column definition (such as a process's Working Set in megabytes), supply a custom formatting expression to the Format-Table cmdlet:

$fields = "Name",@{Label = "WS (MB)"; Expression = {$_.WS / 1mb}; Align = "Right"} 
Get-Process | Format-Table $fields -Auto

Discussion

The Format-Table cmdlet is one of the four PowerShell formatting cmdlets. These cmdlets include Format-Table, Format-List, Format-Wide, and Format-Custom. The Format-Table cmdlet takes input and displays information about that input as a table. By default, PowerShell takes the list of properties to display from the *.format.ps1xml files in PowerShell's installation directory. You can display all properties of the items if you type Format-Table *, although this is rarely a useful view.

2 comments

  1. David "Makovec" Moravec Posted 17 days ago

    Bad font of '*.format.ps1xml'

  2. Lee Holmes Posted 14 days and 19 hours ago

    Thanks. That's actually the font for "replacable" text, so is correct by O'Reilly standards.

Add a comment

The -Auto parameter to Format-Table is a helpful way to automatically format the table in the most readable way possible. It does come at a cost, however. To figure out the best table layout, PowerShell needs to examine each item in the incoming set of items. For small sets of items, this doesn't make much difference, but for large sets (such as a recursive directory listing) it does. Without the -Auto parameter, the Format-Table cmdlet can display items as soon as it receives them. With the -Auto flag, the cmdlet only displays results after it receives all the input.

Perhaps the most interesting feature of the Format-Table cmdlet is illustrated by the last example: the ability to define completely custom table columns. You define a custom table column similarly to the way that you define a custom column list. Rather than specify an existing property of the items, you provide a hashtable. That hashtable includes up to three keys: the column's label, a formatting expression, and alignment. The Format-Table cmdlet shows the label as the column header and uses your expression to generate data for that column. The label must be a string, the expression must be a script block, and the alignment must be either "Left", "Center",or "Right". In the expression script block, the $_ variable represents the current item being formatted.

Note

The Select-Object cmdlet supports a similar hashtable to add calculated properties, but uses Name (rather than Label) as the key to identify the property. After realizing how confusing this was, version two of PowerShell updated both cmdlets to accept both Name and Label.

The expression shown in the last example takes the working set of the current item and divides it by 1 megabyte (1 MB).

One common stumbling block in PowerShell's formatting cmdlets comes from putting them in the middle of a script or pipeline:

PS > Get-Process PowerShell | Format-Table | Sort Name
out-lineoutput : The object of type "Microsoft.PowerShell.Commands.Internal.F
ormat.FormatEntryData" is not valid or not in the correct sequence. This is l
ikely caused by a user-specified "format-*" command which is conflicting with
 the default formatting.

Internally, PowerShell's formatting commands generate a new type of object: Microsoft.PowerShell.Commands.Internal.Format.*. When these objects make it to the end of the pipeline, PowerShell then automatically sends them to an output cmdlet: by default, Out-Default. These Out-* cmdlets assume that the objects arrive in a certain order, so doing anything with the output of the formatting commands causes an error the the output system.

To resolve this problem, try to avoid calling the formatting cmdlets in the middle of a script or pipeline. When you do this, the output of your script no longer lends itself to the object-based manipulation so synonymous with PowerShell. If you want to display formatted output anyway, send the output through the Out-String cmdlet:

Get-Process PowerShell | Format-Table | Out-String -Stream

Object-manipulations commands will still not work (since the objects have been converted to strings), but at least the the script will not generate errors.

For more information about the Format-Table cmdlet, type Get-Help Format-Table. For more information about hashtables, see the section called “Create a Hashtable or Associative Array”. For more information about script blocks, see the section called “Write a Script Block”.

Store Information in Variables

Problem

You want to store the output of a pipeline or command for later use, or to work with it in more detail.

Solution

To store output for later use, store the output of the command in a variable. You can access this information later, or even pass it down the pipeline as though it was the output of the original command:

PS > $result = 2 + 2
PS > $result
4
PS > $processes = Get-Process
PS > $processes.Count
85
PS > $processes | Where-Object { $_.ID -eq 0 }

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)    Id ProcessName
-------  ------    -----      ----- -----   -----     -- -----------
      0       0        0         16     0              0 Idle

Discussion

Variables in PowerShell (and all other scripting and programming languages) let you store the output of something so that you can use it later. A variable name starts with a dollar sign ($) and can be followed by nearly any character. A small set of characters have special meaning to PowerShell, so PowerShell provides a way to make variable names that include even these.

For more information about the syntax and types of PowerShell variables, see the section called “Variables”.

1 comment

  1. Tim Green Posted 14 days and 2 hours ago

    I have yet to find an Appendix A link that works... all that I've tried generate a "Server Error 500 - An internal server error occurred"

Add a comment

You can store the result of any pipeline or command in a variable to use it later. If that command generates simple data (such as a number or string), then the variable contains simple data. If the command generates rich data (such as the objects that represent system processes from the Get-Process cmdlet), then the variable contains that list of rich data. If the command (such as a traditional executable) generates plain text (such as the output of traditional executable), then the variable contains plain text.

Note

If you've stored a large amount of data into a variable but no longer need that data, you can assign the value $null (or anything else) to that variable. That will allow PowerShell to release the memory it was using to store that data.

1 comment

  1. David "Makovec" Moravec Posted 17 days ago

    assigni => assign

Add a comment

In addition to variables that you create, PowerShell automatically defines several variables that represent things such as the location of your profile file, the process ID of PowerShell, and more. For a full list of these automatic variables, type Get-Help about_automatic_variables.

Access Environment Variables

Problem

You want to use an environment variable (such as the system path, or current user's name) in your script or interactive session.

Solution

PowerShell offers several ways to access environment variables.

To list all environment variables, list the children of the env drive:

Get-ChildItem env:

To get an environment variable using a more concise syntax, precede its name with $env:

$env:variablename

i.e.: $env:username

To get an environment variable using its Provider path, supply env: or Environment:: to the Get-ChildItem cmdlet:

Get-ChildItem env:variablename
Get-ChildItem Environment::variablename

Discussion

PowerShell provides access to environment variables through its environment provider. Providers let you work with data stores (such as the registry, environment variables, and aliases) much as you would access the filesystem.

By default, PowerShell creates a drive (called env) that works with the environment provider to let you access environment variables. The environment provider lets you access items in the env: drive as you would any other drive: dir env:\variablename or dir env:variablename. If you want to access the provider directly (rather than go through its drive), you can also type dir Environment::variablename.

However, the most common (and easiest) way to work with environment variables is by typing $env:variablename. This works with any provider but is most typically used with environment variables.

This is because the environment provider shares something in common with several other providers—namely support for the *-Content set of core cmdlets (see Example 3.1, “Working with content on different providers”).

Example 3.1. Working with content on different providers

PS > "hello world" > test
PS > Get-Content test
hello world
PS > Get-Content c:test
hello world
PS > Get-Content variable:ErrorActionPreference
Continue
PS > Get-Content function:more
param([string[]]$paths)
$OutputEncoding = [System.Console]::OutputEncoding

if($paths)
{
    foreach ($file in $paths)
    {
        Get-Content $file | more.com
    }
}
else
{
    $input | more.com
}
PS > Get-Content env:systemroot
C:\WINDOWS

2 comments

  1. David "Makovec" Moravec Posted 16 days and 23 hours ago

    Have other version of more function. Maybe this one is from v1? (can't check now)

  2. Mike Martino Posted 15 days and 20 hours ago

    This example (and a few others) need clean-up. Because you are using the greater-than symbol in 2 different ways, it might help matters if you made the prompt "PS> " (no space between PS and the greater-than, with a trailing space after the greater-than) to denote the Powershell prompt. It would look much cleaner and would be easier for me to follow.

    In the preceding discussion, could you clear up the drive:dir vs. env:variable explanation? At first, I thought "c:test" was a typo, but I tried it out and know it works. I wasn't sure what you were going for by saying "c:test" when I would have though you'd just use "test" (since you're working in the current directory).

Add a comment


For providers that support the content cmdlets, PowerShell lets you interact with this content through a special variable syntax (see Example 3.2, “Using PowerShell's special variable syntax to access content”).

Example 3.2. Using PowerShell's special variable syntax to access content

PS > $function:more
param([string[]]$paths); if(($paths -ne $null) -and ($paths.length -ne 0)) { ...
       Get-Content $local:file | Out-Host -p } } else { $input | Out-Host ...
PS > $variable:ErrorActionPreference
Continue
PS > $c:test
hello world
PS > $env:systemroot
C:\WINDOWS

This variable syntax for content management lets you to both get and set content:

PS > $function:more = { $input | less.exe }
PS > $function:more
$input | less.exe

Now, when it comes to accessing complex provider paths using this method, you'll quickly run into naming issues (even if the underlying file exists):

PS > $c:\temp\test.txt
Unexpected token '\temp\test.txt' in expression or statement.
At line:1 char:17
+ $c:\temp\test.txt <<<<

The solution to that lies in PowerShell's escaping support for complex variable names. To define a complex variable name, enclose it in braces:

PS > ${1234123!@#$!@#$12$!@#$@!} = "Crazy Variable!"
PS > ${1234123!@#$!@#$12$!@#$@!}
Crazy Variable!
PS > dir variable:\1*

Name                              Value
----                              -----
1234123!@#$!@#$12$!@#$@!          Crazy Variable!

… and the content equivalent (assuming that the file exists):

PS > ${c:	emp	est.txt}
hello world

2 comments

  1. Kjartan Þór Kjartansson Posted 16 days and 4 hours ago

    for some reason the t's are being formatted as tabs here

  2. Lee Holmes Posted 14 days and 17 hours ago

    Thanks. It looks like this is a bug in the review system. It appears correctly in the manuscript and the RSS feed :)

Add a comment

Since environment variable names do not contain special characters, this Get-Content variable syntax is the best (and easiest) way to access environment variables.

For more information about working with PowerShell variables, see the section called “Variables”. For more information about working with environment type Get-Help About_Environment_Variable.

Program: Retain Changes to Environment Variables Set by a Batch File

When a batch file modifies an environment variable, cmd.exe retains this change even after the script exits. This often causes problems, as one batch file can accidentally pollute the environment of another. That said, batch file authors sometimes intentionally change the global environment to customize the path and other aspects of the environment to suit a specific task.

However, environment variables are private details of a process and disappear when that process exits. This makes the environment customization scripts mentioned above stop working when you run them from PowerShell—just as they fail to work when you run them from another cmd.exe (for example, cmd.exe /c MyScript.cmd).

The script in Example 3.3, “Invoke-CmdScript.ps1” lets you run batch files that modify the environment and retain their changes even after cmd.exe exits. It accomplishes this by storing the environment variables in a text file once the batch file completes, and then setting all those environment variables again in your PowerShell session.

To run this script, type Invoke-CmdScript Scriptname.cmd or Invoke-CmdScript Scriptname.bat—whichever extension the batch files uses.

1 comment

  1. Tim Green Posted 14 days and 2 hours ago

    A couple spaces missing: should be "Invoke-CmdScript Scriptname.cmd or Invoke-CmdScript Scriptname.bat"

Add a comment

Note

If this is the first time you've run a script in PowerShell, you will need to configure your Execution Policy. For more information about selecting an execution policy, see the section called “Enable Scripting Through an Execution Policy”.

Notice that this script uses the full names for cmdlets: Get-Content, Foreach-Object, Set-Content, and Remove-Item. This makes the script readable and is ideal for scripts that somebody else will read. It is by no means required, though. For quick scripts and interactive use, shorter aliases (such as gc, %, sc, and ri) can make you more productive.

Example 3.3. Invoke-CmdScript.ps1


param([string] $script, [string] $parameters)  

$tempFile = [IO.Path]::GetTempFileName()  

cmd /c " `"$script`" $parameters && set > `"$tempFile`" " 

Get-Content $tempFile | Foreach-Object {   
    if($_ -match "^(.*?)=(.*)$")  
    { 
        Set-Content "env:\$($matches[1])" $matches[2]  
    } 
}  

Remove-Item $tempFile 
      

For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.

Control Access and Scope of Variables and Other Items

Problem

You want to control how you define (or interact with) the visibility of variables, aliases, functions, and drives.

Solution

PowerShell offers several ways to access variables.

To create a variable with a specific scope, supply that scope before the variable name:

$SCOPE:variable = value

To access a variable at a specific scope, supply that scope before the variable name:

$SCOPE:variable

To create a variable that remains even after the script exits, create it in the GLOBAL scope:

$GLOBAL:variable = value

To change a scriptwide variable from within a function, supply SCRIPT as its scope name:

$SCRIPT:variable = value

Discussion

PowerShell controls access to variables, functions, aliases, and drives through a mechanism known as scoping. The scope of an item is another term for its visibility. You are always in a scope (called the current or local scope), but some actions change what that means.

When your code enters a nested prompt, script, function, or script block, PowerShell creates a new scope. That scope then becomes the local scope. When it does this, PowerShell remembers the relationship between your old scope and your new scope. From the view of the new scope, the old scope is called the parent scope. From the view of the old scope, the new scope is called a child scope. Child scopes get access to all the variables in the parent scope, but changing those variables in the child scope doesn't change the version in the parent scope.

Note

Trying to change a scriptwide variable from a function is often a "gotcha," because a function is a new scope. As mentioned previously, changing something in a child scope (the function) doesn't affect the parent scope (the script). The rest of this discussion describes ways to change the value for the entire script.

When your code exits a nested prompt, script, function, or script block, the opposite happens. PowerShell removes the old scope, then changes the local scope to be the scope that originally created it—the parent of that old scope.

Some scopes are so common that PowerShell gives them special names:

Global

The outermost scope. Items in the global scope are visible from all other scopes.

Script

The scope that represents the current script. Items in the script scope are visible from all other scopes in the script.

Local

The current scope.

When you define the scope of an item, PowerShell supports two additional scope names that act more like options: Private and AllScope. When you define an item to have a Private scope, PowerShell does not make that item directly available to child scopes. PowerShell does not hide it from child scopes, though, as child scopes can still use the -Scope parameter of the Get-Variable cmdlet to get variables from parent scopes. When you specify the AllScope option for an item (through one of the *-Variable, *-Alias, or *-Drive cmdlets), child scopes that change the item also affect the value in parent scopes.

With this background, PowerShell provides several ways for you to control access and scope of variables and other items.

Variables

To define a variable at a specific scope (or access a variable at a specific scope), use its scope name in the variable reference. For example:

$SCRIPT:myVariable = value

As illustrated in the section called “Variables”, the *-Variable set of cmdlets also let you specify scope names through their -Scope parameter.

Functions

To define a function at a specific scope (or access a function at a specific scope), use its scope name when creating the function. For example:

function GLOBAL:MyFunction { ... }
GLOBAL:MyFunction args

2 comments

  1. Johannes Rössel Posted 17 days ago

    According to Get-Help about_Scope:

    The syntax for a scope modifier in a variable is:

    $[<scope-modifier>]:<name> = <value>
    

    The syntax for a scope modifier in a function is:

    function [<scope-modifier>]:<name> {<function-body>}
    

    That is, the function definition with a scope does not start with a $ sigil.

  2. Lee Holmes Posted 14 days and 19 hours ago

    Great catch! Thanks.

Add a comment

Aliases and drives

To define an alias or drive at a specific scope, use the Option parameter of the *-Alias and *-Drive cmdlets. To access an alias or drive at a specific scope, use the Scope parameter of the *-Alias and *-Drive cmdlets.

For more information about scopes, type Get-Help About-Scope.

Program: Create a Dynamic Variable

When working with variables and commands, some concepts feel too minor to deserve an entire new command or function, but the readablility of your script suffers without them.

A few examples where this becomes evident are date math (yesterday becomes (Get-Date).AddDays(-1)), and deeply-nested variables (window title becomes $host.UI.RawUI.WindowTitle.)

Although we could write our own extensions to make these easier to access, Get-Yesterday, Get-WindowTitle, and Set-WindowTitle feel too insignificant to deserve their own commands.

PowerShell lets you define your own types of variables by extending its PSVariable class, but that functionality is largely designed for developer scenarios, and not for scripting scenarios. Example 3.4, “New-DynamicVariable.ps1” resolves this quandary by creating a new variable type (DynamicVariable) that supports dynamic script actions when you get or set the variable's value.

Example 3.4. New-DynamicVariable.ps1


<#

.SYNOPSIS
Creates a variable that supports scripted actions for its getter and setter

.EXAMPLE
PS >.\New-DynamicVariable GLOBAL:WindowTitle `
     -Getter { $host.UI.RawUI.WindowTitle } `
     -Setter { $host.UI.RawUI.WindowTitle = $args[0] }

PS >$windowTitle
Administrator: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
PS >$windowTitle = "Test"
PS >$windowTitle
Test
  
#>

param(
    [Parameter(Mandatory = $true)]
    $Name,
    
    [Parameter(Mandatory = $true)]
    [ScriptBlock] $Getter,
    
    [ScriptBlock] $Setter)

Add-Type @"
using System;
using System.Collections.ObjectModel;
using System.Management.Automation;

namespace Lee.Holmes
{
    public class DynamicVariable : PSVariable
    {
        public DynamicVariable(
            string name,
            ScriptBlock scriptGetter,
            ScriptBlock scriptSetter)
                : base(name, null, ScopedItemOptions.AllScope)
        {
            getter = scriptGetter;
            setter = scriptSetter;
        }
        private ScriptBlock getter;
        private ScriptBlock setter;

        public override object Value
        {
            get
            {
                if(getter != null)
                {
                    Collection<PSObject> results = getter.Invoke();
                    if(results.Count == 1)
                    {
                        return results[0];
                    }
                    else
                    {
                        PSObject[] returnResults = new PSObject[results.Count];
                        results.CopyTo(returnResults, 0);
                        return returnResults;
                    }
                }
                else { return null; }
            }
            set
            {
                if(setter != null) { setter.Invoke(value); }
            }
        }
    }
}
"@

if(Test-Path variable:\$name)
{
    Remove-Item variable:\$name -Force
}
$executioncontext.SessionState.PSVariable.Set(
    (New-Object Lee.Holmes.DynamicVariable $name,$getter,$setter))

      

Work with .NET Objects

Problem

You want to use and interact with one of the features that make PowerShell so powerful—its intrinsic support for .NET objects.

Solution

PowerShell offers ways to access methods (both static and instance) and properties.

To call a static method on a class, place the type name in square brackets, and then separate the class name from the method name with two colons:

[ClassName]::MethodName(parameter list)

To call a method on an object, place a dot between the variable that represents that object and the method name:

$objectReference.MethodName(parameter list)

To access a static property on a class, place the type name in square brackets, and then separate the class name from the property name with two colons:

[ClassName]::PropertyName

To access a property on an object, place a dot between the variable that represents that object and the property name:

$objectReference.PropertyName

Discussion

One feature that gives PowerShell its incredible reach into both system administration and application development is its capability to leverage Microsoft's enormous and broad .NET Framework. The .NET Framework is a large collection of classes. Each class embodies a specific concept and groups closely related functionality and information. Working with the .NET Framework is one aspect of PowerShell that introduces a revolution to the world of management shells.

An example of a class from the .NET Framework is System.Diagnostics.Process—the grouping of functionality that "provides access to local and remote processes, and enables you to start and stop local system processes."

Note

The terms type and class are often used interchangeably.

Classes contain methods (which let you perform operations) and properties (which let you access information).

For example, the Get-Process cmdlet generates System.Diagnostics.Process objects, not a plain-text report like traditional shells. Managing these processes becomes incredibly easy, as they contain a rich mix of information (properties) and operations (methods). You no longer have to parse a stream of text for the ID of a process—you can just ask the object directly!

PS > $process = Get-Process Notepad
PS > $process.Id
3872

Static methods

[ClassName]::MethodName(parameter list)

Some methods apply only to the concept the class represents. For example, retrieving all running processes on a system relates to the general concept of processes, instead of a specific process. Methods that apply to the class/type as a whole are called static methods.

For example:

PS > [System.Diagnostics.Process]::GetProcessById(0)

This specific task is better handled by the Get-Process cmdlet, but it demonstrates PowerShell's capability to call methods on .NET classes. It calls the static GetProcessById method on the System.Diagnostics.Process class to get the process with the ID of 0. This generates the following output:

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
      0      0     0    16     0         0 Idle

Instance methods

$objectReference.MethodName(parameter list)

Some methods relate only to specific, tangible realizations (called instances) of a class. An example of this would be stopping a process actually running on the system, as opposed to the general concept of processes. If $objectReference refers to a specific System.Diagnostics.Process (as output by the Get-Process cmdlet, for example), you may call methods to start it, stop it, or wait for it to exit. Methods that act on instances of a class are called instance methods.

Note

The term object is often used interchangeably with the term instance.

For example:

PS > $process = Get-Process Notepad
PS > $process.WaitForExit()

Stores the notepad process into the $process variable. It then calls the WaitForExit() instance method on that specific process to pause PowerShell until the process exits.

1 comment

  1. David "Makovec" Moravec Posted 16 days and 23 hours ago

    Stores notepad process into the $process variable.

Add a comment

Note

To learn about the different sets of parameters (overloads) that a given method supports, type that method name without any parameters. For an even cleaner view, access the OverloadDefinitions property of the method:

PS > $now = Get-Date
PS > $now.AddDays

MemberType : Method
OverloadDefinitions : {System.DateTime AddDays(Double value)}
TypeNameOfValue : System.Management.Automation.PSMethod
Value : System.DateTime AddDays(Double value)
Name : AddDays
IsInstance : True

PS > $now.AddDays.OverloadDefinitions
System.DateTime AddDays(double value)

For both static methods and instance methods, you may sometimes run into situations where PowerShell either generates an error, or fails to invoke the method you expected it to. In this case, review the output of the Trace-Command cmdlet, with MemberResolution as the trace type.

Example 3.5. Investigating PowerShell's method resolution

PS > Trace-Command MemberResolution -PsHost {
>>     [System.Diagnostics.Process]::GetProcessById(0) }
>>
DEBUG: MemberResolution Information: 0 : cache hit, Calling Method: static
 System.Diagnostics.Process GetProcessById(int processId)
DEBUG: MemberResolution Information: 0 : Method argument conversion.
DEBUG: MemberResolution Information: 0 :     Converting parameter "0" to
"System.Int32".
DEBUG: MemberResolution Information: 0 : Checking for possible references.

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
      0       0        0         12     0               0 Idle


Static properties

[ClassName]::PropertyName

or

[ClassName]::PropertyName = value

Like static methods, some properties relate only to information about the concept that the class represents. For example, the System.DateTime class "represents an instant in time, typically expressed as a date and time of day." It provides a Now static property that returns the current time:

PS > [System.DateTime]::Now
Saturday, June 2, 2010 4:57:20 PM

This specific task is better handled by the Get-Date cmdlet, but it demonstrates PowerShell's capability to access properties on .NET objects.

Although relatively rare, some types let you set the value of some static properties as well: for example, the [System.Environment]::CurrentDirectory property. This property represents the process's current directory—which represents PowerShell's startup directory, as opposed to the path you see in your prompt.

Instance properties

$objectReference.PropertyName

or

$objectReference.PropertyName = value

Like instance methods, some properties relate only to specific, tangible realizations (called instances) of a class. An example of this would be the day of an actual instant in time, as opposed to the general concept of dates and times. If $objectReference refers to a specific System.DateTime (as output by the Get-Date cmdlet or [System.DateTime]::Now, for example), you may want to retrieve its day of week, day, or month. Properties that return information about instances of a class are called instance properties.

1 comment

  1. Johannes Rössel Posted 17 days ago

    Formatting is messed up a bit around [System.DateTime]::Now—the :: isn't formatted as code, neither are the square brackets. And there shouldn't be a space in [System.DateTime] either.

Add a comment

For example:

PS > $today = Get-Date
PS > $today.DayOfWeek
Saturday

This example stores the current date in the $today variable. It then calls the DayOfWeek instance property to retrieve the day of the week for that specific date.

With this knowledge, the next questions are: "How do I learn about the functionality available in the .NET Framework?" and "How do I learn what an object does?"

For an answer to the first question, see Appendix F, Selected .NET Classes and Their Uses for a hand-picked list of the classes in the .NET Framework most useful to system administrators. For an answer to the second, see and the section called “Get Detailed Documentation About Types and Objects”.

Create an Instance of a .NET Object

Problem

You want to create an instance of a .NET object to interact with its methods and properties.

Solution

Use the New-Object cmdlet to create an instance of an object.

To create an instance of an object using its default constructor, use the New-Object cmdlet with the class name as its only parameter:

PS > $generator = New-Object System.Random
PS > $generator.NextDouble()
0.853699042859347

To create an instance of an object that takes parameters for its constructor, supply those parameters to the New-Object cmdlet. In some instances, the class may exist in a separate library not loaded in PowerShell by default, such as the System.Windows.Forms assembly. In that case, you must first load the assembly that contains the class:

Add-Type -Assembly System.Windows.Forms
$image = New-Object System.Drawing.Bitmap source.gif
$image.Save("source_converted.jpg","JPEG")

To create an object and use it at the same time (without saving it for later), wrap the call to New-Object in parentheses:

PS > (New-Object Net.WebClient).DownloadString("http://live.com")

Discussion

Many cmdlets (such as Get-Process and Get-ChildItem) generate live .NET objects that represent tangible processes, files, and directories. However, PowerShell supports much more of the .NET Framework than just the objects that its cmdlets produce. These additional areas of the .NET Framework supply a huge amount of functionality that you can use in your scripts and general system administration tasks.

When it comes to using most of these classes, the first step is often to create an instance of the class, store that instance in a variable, and then work with the methods and properties on that instance. To create an instance of a class, you use the New-Object cmdlet. The first parameter to the New-Object cmdlet is the type name, and the second parameter is the list of arguments to the constructor, if it takes any. The New-Object cmdlet supports PowerShell's type shortcuts, so you never have to use the fully qualified type name. For more information about type shortcuts, see the section called “Work with .NET Objects”.

Note

A common pattern when working with .NET objects is to create them, set a few properties, and then use them. The -Property parameter of the New-Object cmdlet lets you combine both steps:

$startInfo = New-Object Diagnostics.ProcessStartInfo -Property @{
    'Filename' = "powershell.exe";
    'WorkingDirectory' = $pshome;
    'Verb' = "RunAs"
}
[Diagnostics.Process]::Start($startInfo)

Since the second parameter to the New-Object cmdlet is an array of parameters to the type's constructor, you might encounter difficulty when trying to specify a parameter that itself is a list. Assuming $byte is an array of bytes:

PS > $memoryStream = New-Object System.IO.MemoryStream $bytes
New-Object : Cannot find an overload for ".ctor" and the argument count: "11".
At line:1 char:27
+ $memoryStream = New-Object <<<< System.IO.MemoryStream $bytes

To solve this, provide an array that contains an array:

PS > $parameters = ,$bytes
PS > $memoryStream = New-Object System.IO.MemoryStream $parameters

or

PS > $memoryStream = New-Object System.IO.MemoryStream @(,$bytes)

Load types from another assembly

PowerShell makes most common types available by default. However, many are available only after you load the library (called the assembly) that defines them. The MSDN documentation for a class includes the assembly that defines it. For more information about loading types from another assembly, see the section called “Access a .NET SDK Library”.

For a hand-picked list of the classes in the .NET Framework most useful to system administrators, see Appendix F, Selected .NET Classes and Their Uses. To learn more about the functionality that a class supports, see the section called “Learn About Types and Objects”.

For more information about the New-Object cmdlet, type Get-Help New-Object. For more information about the Add-Type cmdlet, type Get-Help Add-Type.

Program: Create Instances of Generic Objects

When you work with the .NET Framework, you'll often run across classes that have the primary responsibility of managing other objects. For example, the System.Collections.ArrayList class lets you manage a dynamic list of objects. You can add objects to an ArrayList, remove objects from it, sort the objects inside, and more. These objects can be any type of object: String objects, integers, DateTime objects, and many more. However, working with classes that support arbitrary objects can sometimes be a little awkward. One example is type safety: if you accidentally add a String to a list of integers, you might not find out until your program fails.

Although the issue becomes largely moot when working only inside PowerShell, a more common complaint in strongly typed languages (such as C#) is that you have to remind the environment (through explicit casts) about the type of your object when you work with it again:

// This is C# code
System.Collections.ArrayList list =
    new System.Collections.ArrayList();
list.Add("Hello World");

string result = (String) list[0];

To address these problems, the .NET Framework introduced a feature called generic types: classes that support arbitrary types of objects, but let you specify which type of object. In this case, a collection of strings:

// This is C# code
System.Collections.ObjectModel.Collection<String> list =
    new System.Collections.ObjectModel.Collection<String>();
list.Add("Hello World");

string result = list[0];

PowerShell version one did not handle this directly, but version two lets you define generic parameters by placing them between square brackets:

Example 3.6. Creating a generic object

PS > $coll = New-Object System.Collections.ObjectModel.Collection[Int]
PS > $coll.Add(15)
PS > $coll.Add("Test")
Cannot convert argument "0", with value: "Test", for "Add" to type "System
.Int32": "Cannot convert value "Test" to type "System.Int32". Error: "Inpu
t string was not in a correct format.""
At line:1 char:10
+ $coll.Add <<<< ("Test")
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

For a generic type that takes two or more parameters, provide a comma-separated list of types, enclosed in quotes:

Example 3.7. Creating a multi-parameter generic object

PS > $map = New-Object System.Collections.Generic.Dictionary["String,Int"]
PS > $map.Add("Test", 15)
PS > $map.Add("Test2", "Hello")
Cannot convert argument "1", with value: "Hello", for "Add" to type "Syste
m.Int32": "Cannot convert value "Hello" to type "System.Int32". Error: "In
put string was not in a correct format.""
At line:1 char:9
+ $map.Add <<<< ("Test2", "Hello")
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

PowerShell version one does not support generic types very elegantly. For a simple generic type, you can use the syntax that the .NET Framework uses under the hood:

1 comment

  1. Mike Martino Posted 15 days and 20 hours ago

    Powershell v1 might stand out more than "powershell one". Btw, I like how you took the time to point this out. Maybe you should make these like those little blurbs with a warning icon so that it really stands out. Or make a special icon for 1, another for 2 to help you highlight the differences.

Add a comment

$coll = New-Object 'System.Collections.ObjectModel.Collection`1[System.String]'

However, that begins to fall apart if you want to use types defined outside the main mscorlib assembly, or want to create complex generic types (for example, ones that refer to other generic types).

Example 3.8, “New-GenericObject.ps1” lets you to easily create instances of generic types.

Example 3.8. New-GenericObject.ps1


param(
    [string] $typeName = $(throw "Please specify a generic type name"),
    [string[]] $typeParameters = $(throw "Please specify the type parameters"),
    [object[]] $constructorParameters
    )

$genericTypeName = $typeName + '`' + $typeParameters.Count
$genericType = [Type] $genericTypeName

if(-not $genericType)
{
    throw "Could not find generic type $genericTypeName"
}

[type[]] $typedParameters = $typeParameters
$closedType = $genericType.MakeGenericType($typedParameters)
if(-not $closedType)
{
    throw "Could not make closed type $genericType"
}

,[Activator]::CreateInstance($closedType, $constructorParameters)

      

Reduce Typing for Long Class Names

Problem

You want to reduce the amount of redundant information in your script when you interact with classes that have long type names.

Solution

To reduce typing for static methods, store the type name in a variable:

$math = [System.Math]
$math::Min(1,10)
$math::Max(1,10)

To reduce typing for multiple objects in a namespace, use the -f (format) operator:

$namespace = "System.Collections.{0}"
$arrayList = New-Object ($namespace -f "ArrayList")
$queue = New-Object ($namespace -f "Queue")

To reduce typing for static methods of multiple types in a namespace, use the -f (format) operator along with a cast:

$namespace = "System.Diagnostics.{0}"
([Type] ($namespace -f "EventLog"))::GetEventLogs()
([Type] ($namespace -f "Process"))::GetCurrentProcess()

Discussion

One thing you will notice when working with some .NET classes (or classes from a third-party SDK), is that it quickly becomes tiresome to specify their fully qualified type names. For example, many useful collection classes in the .NET Framework all start with "System.Collections". This is called the namespace of that class. Most programming languages solve this problem with a using directive that lets you to specify a list of namespaces for that language to search when you type a plain class name such as "ArrayList". PowerShell lacks a using directive, but there are several options to get the benefits of one.

If you are repeatedly working with static methods on a specific type, you can store that type in a variable to reduce typing as shown in the solution:

$math = [System.Math]
$math::Min(1,10)
$math::Max(1,10)

If you are creating instances of different classes from a namespace, you can store the namespace in a variable and then use the PowerShell -f (format) operator to specify the unique class name:

$namespace = "System.Collections.{0}"
$arrayList = New-Object ($namespace -f "ArrayList")
$queue = New-Object ($namespace -f "Queue")

If you are working with static methods from several types in a namespace, you can store the namespace in a variable, use the -f (format) operator to specify the unique class name, and then finally cast that into a type:

$namespace = "System.Diagnostics.{0}"
([Type] ($namespace -f "EventLog"))::GetEventLogs()
([Type] ($namespace -f "Process"))::GetCurrentProcess()

For more information about PowerShell's format operator, see the section called “Place Formatted Information in a String”.

Use a COM Object

Problem

You want to create a COM object to interact with its methods and properties.

Solution

Use the New-Object cmdlet (with the –ComObject parameter) to create a COM object from its ProgID. You can then interact with the methods and properties of the COM object as you would any other object in PowerShell.

$object = New-Object -ComObject ProgId

For example:

PS > $sapi = New-Object -Com Sapi.SpVoice
PS > $sapi.Speak("Hello World")

Discussion

Historically, many applications have exposed their scripting and administration interfaces as COM objects. While .NET APIs (and PowerShell cmdlets) are becoming more common, interacting with COM objects is still a common administrative task.

As with classes in the .NET Framework, it is difficult to know what COM objects you can use to help you accomplish your system administration tasks. For a hand-picked list of the COM objects most useful to system administrators, see Appendix H, Selected COM Objects and Their Uses.

For more information about the New-Object cmdlet, type Get-Help New-Object.

Learn About Types and Objects

Problem

You have an instance of an object and want to know what methods and properties it supports.

Solution

The most common way to explore the methods and properties supported by an object is through the Get-Member cmdlet.

To get the instance members of an object you've stored in the $object variable, pipe it to the Get-Member cmdlet:

$object | Get-Member
Get-Member -InputObject $object

1 comment

  1. David "Makovec" Moravec Posted 16 days and 22 hours ago

    Get-Member -InputObject $object

Add a comment

To get the static members of an object you've stored in the $object variable, supply the -Static flag to the Get-Member cmdlet:

$object | Get-Member -Static
Get-Member -Static -InputObject $object

To get the static members of a specific type, pipe that type to the Get-Member cmdlet, and also specify the –Static flag:

1 comment

  1. David "Makovec" Moravec Posted 16 days and 22 hours ago

    space before '-Static'

Add a comment

[Type] | Get-Member -Static
Get-Member -InputObject [Type]

To get members of the specified member type (for example, Method, Property) from an object you have stored in the $object variable, supply that member type to the –MemberType parameter:

$object | Get-Member -MemberType MemberType
Get-Member -MemberType memberType -InputObject $object

1 comment

  1. David "Makovec" Moravec Posted 16 days and 22 hours ago

    $object | Get-Member -MemberType memberType

Add a comment

Discussion

The Get-Member cmdlet is one of the three commands you will use most commonly as you explore Windows PowerShell. The other two commands are Get-Command and Get-Help.

If you pass the Get-Member cmdlet a collection of objects (such as an Array or ArrayList) through the pipeline, PowerShell extracts each item from the collection, and then passes them to the Get-Member cmdlet one-by-one. The Get-Member cmdlet then returns the members of each unique type that it receives. Although helpful the vast majority of the time, this sometimes causes difficulty when you want to learn about the members or properties of the collection class itself.

If you want to see the properties of a collection (as opposed to the elements it contains), provide the collection to the –InputObject parameter, instead. Alternatively, you may wrap the collection in an array (using PowerShell's unary comma operator) so that the collection class remains when the Get-Member cmdlet unravels the outer array:

PS > $files = Get-ChildItem
PS > ,$files | Get-Member

   TypeName: System.Object[]

Name               MemberType     Definition
----               ----------     ----------
Count              AliasProperty  Count = Length
Address            Method         System.Object& Address(Int32 )
(...)

For another way to learn detailed information about types and objects, see the section called “Get Detailed Documentation About Types and Objects”.

For more information about the Get-Member cmdlet, type Get-Help Get-Member.

Get Detailed Documentation About Types and Objects

Problem

You have a type of object and want to know detailed information about the methods and properties it supports.

Solution

The documentation for the .NET Framework (available on http://msdn.microsoft.com) is the best way to get detailed documentation about the methods and properties supported by an object. That exploration generally comes in two stages:

  1. Find the type of the object.

    To determine the type of an object, you can use either the type name shown by the Get-Member cmdlet (as described in the section called “Learn About Types and Objects”), or call the GetType() method of an object (if you have an instance of it):

    PS > $date = Get-Date
    PS > $date.GetType().ToString()
    System.DateTime
  1. Enter that type name into the search box at http://msdn.microsoft.com.

Discussion

When the Get-Member cmdlet does not provide the information you need, the MSDN documentation for a type is a great alternative. It provides much more detailed information than the help offered by the Get-Member cmdlet—usually including detailed descriptions, related information, and even code samples. MSDN documentation focuses on developers using these types through a language such as C#, though, so you may find interpreting the information for use in PowerShell to be a little difficult at first.

Typically, the documentation for a class first starts with a general overview, and then provides a hyperlink to the members of the class—the list of methods and properties it supports.

Note

To get to the documentation for the members quickly, search for them more explicitly by adding the term "members" to your MSDN search term:

typename members

Documentation for the members of a class lists its methods and properties, as does the output of the Get-Member cmdlet. The S icon represents static methods and properties. Click the member name for more information about that method or property.

Public constructors

This section lists the constructors of the type. You use a constructor when you create the type through the New-Object cmdlet. When you click on a constructor, the documentation provides all the different ways that you can create that object, including the parameter list that you will use with the New-Object cmdlet.

Public fields/public properties

This section lists the names of the fields and properties of an object. The S icon represents a static field or property. When you click on a field or property, the documentation also provides the type returned by this field or property.

For example, you might see the following in the definition for System.DateTime.Now:

C#
public static DateTime Now { get; }

Public means that the Now property is public—that everybody can access it. Static means that the property is static (as described in the section called “Work with .NET Objects”). DateTime means that the property returns a DateTime object when you call it. Get; means that you can get information from this property but cannot set the information. Many properties support a Set; as well (such as the IsReadOnly property on System.IO.FileInfo), which means that you can change its value.

Public methods

This section lists the names of the methods of an object. The S icon represents a static method. When you click on a method, the documentation provides all the different ways that you can call that method, including the parameter list that you will use to call that method in PowerShell.

For example, you might see the following in the definition for System.DateTime.AddDays():

C#
public DateTime AddDays (
    double value
)

Public means that the AddDays method is public—that everybody can access it. DateTime means that the method returns a DateTime object when you call it. The text, double value, means that this method requires a parameter (of type double). In this case, that parameter determines the number of days to add to the DateTime object on which you call the method.

Add Custom Methods and Properties to Objects

Problem

You have an object and want to add your own custom properties or methods (members) to that object.

Solution

Use the Add-Member cmdlet to add custom members to an object.

Discussion

The Add-Member cmdlet is extremely useful in helping you add custom members to individual objects. For example, imagine that you want to create a report from the files in the current directory, and that report should include each file's owner. The Owner property is not standard on the objects that Get-ChildItem produces, but you could write a small script to add them, as shown in Example 3.9, “A script that adds custom properties to its output of file objects”.

Example 3.9. A script that adds custom properties to its output of file objects


$files = Get-ChildItem
foreach($file in $files)
{
    $owner = (Get-Acl $file).Owner
    $file | Add-Member NoteProperty Owner $owner
    $file
}
	

2 comments

  1. David "Makovec" Moravec Posted 16 days and 13 hours ago

    It shows standard properties. Should be: $file | select Name, Owner

  2. Lee Holmes Posted 14 days and 18 hours ago

    Thanks. The second line in the example illustrates how to show the owner.

Add a comment


For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.

Although it is most common to add static information (such as a NoteProperty), the Add-Member cmdlet supports several other property and method types—including AliasProperty, ScriptProperty, CodeProperty, CodeMethod, and ScriptMethod. For a more detailed description of these other property types, see the section called “Working with the .NET Framework”, as well as the help documentation for the Add-Member cmdlet.

Note

To create entirely new objects (instead of adding information to existing ones), see the section called “Create and Initialize Custom Objects”.

Although the Add-Member cmdlet lets you to customize specific objects, it does not let you customize all objects of that type. For information on how to do that, see the following section the section called “Add Custom Methods and Properties to Types”.

Calculated properties

Calculated properties are another useful way to add information to output objects. If your script or command uses a Format-Table or Select-Object command to generate its output, you can create additional properties by providing an expression that generates their value. For example:

Get-ChildItem |
  Select-Object Name,
      @{Name="Size (MB)"; Expression={ "{0,8:0.00}" -f ($_.Length / 1MB) } }

In this command, we get the list of files in the directory. We use the Select-Object command to retrieve its name and a calculated property called Size (MB). This calculated property returns the size of the file in megabytes, rather than the default (which is bytes).

Note

The Format-Table cmdlet supports a similar hashtable to add calculated properties, but uses Label (rather than Name) as the key to identify the property. After realizing how confusing this was, version two of PowerShell updated both cmdlets to accept both Name and Label.

For more information about the Add-Member cmdlet, type Get-Help Add-Member.

For more information about adding calculated properties, type Get-Help Select-Object or Get-Help Format-Table.

Create and Initialize Custom Objects

Problem

You want to return structured results from a command so that users can easily sort, group, and filter them.

Solution

Use the New-Object cmdlet to create a new PsObject, and then supply a hashtable with the custom information to the -Property parameter:

Example 3.10. Creating a custom object

$output = @{
    'User' = 'DOMAIN\User';
    'Quota' = 100MB;
    'ReportDate' = Get-Date;
}

New-Object PsObject -Property $output

If you want to create a custom object with associated functionality, place the functionality in a module, and load that module with the -AsCustomObject parameter.

$obj = Import-Module PlottingObject -AsCustomObject
$obj.Move(10,10)

$obj.Points = SineWave
while($true) { $obj.Rotate(10); $obj.Draw(); Sleep -m 20 }

Discussion

When your script outputs information to the user, always prefer richly-structured data over hand-formatted reports. By emitting custom objects, you give the end-user as much control over your script's output as PowerShell gives you over the output of its own commands.

Despite the power enabled by the output of custom objects, user-written scripts have frequently continued to generate plain text output. This can be partly blamed on PowerShell's previously cumbersome support for the creation and initialization of custom objects.

Example 3.11. Creating a custom object in PowerShell version one

$output = New-Object PsObject
Add-Member -InputObject $output NoteProperty User 'DOMAIN\user'
Add-Member -InputObject $output NoteProperty Quota 100MB
Add-Member -InputObject $output NoteProperty ReportDate (Get-Date)

$output

In PowerShell version one, creating a custom object required creating a new object (of the type PsObject), and then calling the Add-Member cmdlet multiple times to add the desired information. As shown in the solution, PowerShell version two adds the -Property parameter to the New-Object cmdlet that makes this immensely easier.

While creating a new PsObject makes it easy to create data-centric objects (often called property bags), it does not let you add functionality to those objects. When you need functionality as well, the next step is to create a module, and import that module with the -AsCustomObject parameter. Any variables exported by that module become properties on the resulting object, and any functions exported by that module become methods on the resulting object.

Note

An important point about importing a module as a custom object is that variables defined in that custom object are shared by all versions of that object. If you import the module again as a custom object (but store the result in another variable), the two objects will share their internal state.

Example 3.12. Creating a module designed to be used as a custom object


<#

.EXAMPLE

Remove-Module PlottingObject
function SineWave { -15..15 | % { ,($_,(10 * [Math]::Sin($_ / 3))) } }
function Box { -5..5 | % { ($_,-5),($_,5),(-5,$_),(5,$_) } }

$obj = Import-Module PlottingObject -AsCustomObject
$obj.Move(10,10)

$obj.Points = SineWave
while($true) { $obj.Rotate(10); $obj.Draw(); Sleep -m 20 }

$obj.Points = Box
while($true) { $obj.Rotate(10); $obj.Draw(); Sleep -m 20 }

#>

$SCRIPT:x = 0
$SCRIPT:y = 0
$SCRIPT:angle = 0
$SCRIPT:xScale = -50,50
$SCRIPT:yScale = -50,50

$SCRIPT:Points = @()
Export-ModuleMember -Variable Points

function Rotate($angle)
{
    $SCRIPT:angle += $angle
}
Export-ModuleMember -Function Rotate

function Move($xDelta, $yDelta)
{
    $SCRIPT:x += $xDelta
    $SCRIPT:y += $yDelta
}
Export-ModuleMember -Function Move

function Draw
{
    $degToRad = 180 * [Math]::Pi
    Clear-Host

    PutPixel 0 0 +

    foreach($point in $points)
    {
        $pointX,$pointY = $point
        $pointX = $pointX + $SCRIPT:x
        $pointY = $pointY + $SCRIPT:y

        $newX = $pointX * [Math]::Cos($SCRIPT:angle / $degToRad ) -
            $pointY * [Math]::Sin($SCRIPT:angle / $degToRad )
        $newY = $pointY * [Math]::Cos($SCRIPT:angle / $degToRad ) +
            $pointX * [Math]::Sin($SCRIPT:angle / $degToRad )

        PutPixel $newX $newY O
    }

    [Console]::WriteLine()
}
Export-ModuleMember -Function Draw

function PutPixel($x, $y, $character)
{
    $scaledX = ($x - $xScale[0]) / ($xScale[1] - $xScale[0])
    $scaledX *= [Console]::WindowWidth

    $scaledY = (($y * 4 / 3) - $yScale[0]) / ($yScale[1] - $yScale[0])
    $scaledY *= [Console]::WindowHeight

    try
    {
        [Console]::SetCursorPosition($scaledX,
            [Console]::WindowHeight - $scaledY)
        [Console]::Write($character)
    }
    catch
    {
    }
}
      


For more information about creating modules, see the section called “Package Common Commands in a Module”.

If neither of these options suit your requirements (or if you need to create an object that can be consumed by other .NET libraries), use the Add-Type cmdlet. For more information about this approach, see the section called “Define or Extend a .NET Class”.

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 custom type extension files to add custom members to all objects of a type.

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 extension files.

Type extensions are simple XML files that PowerShell interprets. They let you (as the administrator of the system) 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.

Note

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 example, imagine a script that returns the free disk space on a given drive. That might be helpful as a script, but you might find it easier to instead make PowerShell's PSDrive objects themselves tell you how much free space they have left.

Getting started

If you haven't already, the first step in creating a types 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 name Types.Custom.ps1xml, as shown in Example 3.13, “Sample Types.Custom.ps1xml file”.

Example 3.13. 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 the Types.ps1xml file in PowerShell's installation directory. 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 version two 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, as shown in Example 3.14, “A template for changes to a custom types file”. The System.Management.Automation.PSDriveInfo type is the type that the Get-PSDrive cmdlet generates.

Example 3.14. 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 to 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 right-hand side of the assignment.

Example 3.15, “A ScriptProperty for the PSDriveInfo type” adds an AvailableFreeSpace ScriptProperty to PSDriveInfo, and should be placed within the members section of the template given in Example 3.14, “A template for changes to a custom types file”. 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.15. A ScriptProperty for the PSDriveInfo type

<ScriptProperty>
  <Name>AvailableFreeSpace</Name>
  <GetScriptBlock>
    if($this.Provider.ImplementingType -eq
       [Microsoft.PowerShell.Commands.FileSystemProvider])
    {
       $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>
   $availableFreeSpace = $this.AvailableFreeSpace

   $spaceDifference = (([long] $args[0]) - $availableFreeSpace) / 1MB

   if($spaceDifference -gt 0)
   {
       $message = "To obtain $args bytes of free space, " +
          " free $spaceDifference megabytes."
       Write-Host $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 does not 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.16, “An AliasProperty for the PSDriveInfo type” adds a Free AliasProperty to PSDriveInfo, and should also be placed within the members section of the template given in Example 3.14, “A template for changes to a custom types file”. 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.16. 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.17, “A ScriptMethod for the PSDriveInfo type” adds a Remove ScriptMethod to PSDriveInfo. Like the other additions, place these customizations within the members section of the template given in Example 3.14, “A template for changes to a custom types file”. 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.17. A ScriptMethod for the PSDriveInfo type

<ScriptMethod>
  <Name>Remove</Name>
  <Script>
    $force = [bool] $args[0]
    if($force)
    {
       $this | Remove-PSDrive
    }
    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 Windows PowerShell SDK, or MSDN documentation.

You must sign in or register before commenting
*
*
*
*
*

Atom Icon Comments on this page or Comments on the whole book.