As touched on in Chapter 2, 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.
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.
Diag
nostics.
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).
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.
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 following commands:
$currentError = $error[0] $currentError | Format-List -Force
Many commands by default display a
summarized view of their output in a table format, for example, the
Get-Process
cmdlet:
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 are
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. Format.FormatEntryData" is not valid or not in the correct sequence. This is likely 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
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 in 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 use the formatted output directly, send the
output through the Out-String
cmdlet
as described in Program:
Search Formatted Output for a Pattern.
For more information about the Format-List
cmdlet, type Get-Help Format-List
.
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.
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
WorkingSet
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
The Format-Table
cmdlet is one of the four PowerShell
formatting cmdlets. These cmdlets are 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.
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 displays results only 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 $_
(or $PSItem
) 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 2 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 | Format-Table | Sort Name out-lineoutput : The object of type "Microsoft.PowerShell.Commands.Internal. Format.FormatEntryData" is not valid or not in the correct sequence. This is likely 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 in 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 use the formatted output directly, send the
output through the Out-String
cmdlet
as described in Program:
Search Formatted Output for a Pattern.
For more information about the Format-Table
cmdlet, type Get-Help Format-Table
. For more
information about hashtables, see Create a Hashtable or
Associative Array. For more information about script blocks,
see Write a Script Block.
You want to store the output of a pipeline or command for later use or to work with it in more detail.
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 were the output of the original command:
PS > $result = 2 + 2 PS > $result 4 PS > $output = ipconfig PS > $output | Select-String "Default Gateway" | Select -First 1 Default Gateway . . . . . . . . . : 192.168.11.1 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
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 Variables.
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, assign a new value (such as $null
) to that variable. That will allow
PowerShell to release the memory it was using to store that
data.
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
.
You want to use an environment variable (such as the system path or the current user’s name) in your script or interactive session.
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
(For example, $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
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:
. This
works with any provider but is most typically used with environment
variables.variablename
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).
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
For providers that support the content cmdlets, PowerShell lets you interact with this content through a special variable syntax (see Example 3-2).
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 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!
The following is the content equivalent (assuming that the file exists):
PS > ${c:\temp\test.txt}
hello world
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 Variables. For more information about working with
environment variables, type Get-Help
About_Environment_Variable
.
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 earlier 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
MyEnvironmentCustomizer.cmd
).
The script in Example 3-3 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
or
Scriptname.cmd
Invoke-
CmdScript
—whichever
extension the batch files uses.Scriptname.bat
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 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
############################################################################## ## ## Invoke-CmdScript ## ## From Windows PowerShell Cookbook (O'Reilly) ## by Lee Holmes (http://www.leeholmes.com/guide) ## ############################################################################## <# .SYNOPSIS Invoke the specified batch file (and parameters), but also propagate any environment variable changes back to the PowerShell environment that called it. .EXAMPLE PS > type foo-that-sets-the-FOO-env-variable.cmd @set FOO=%* echo FOO set to %FOO%. PS > $env:FOO PS > Invoke-CmdScript "foo-that-sets-the-FOO-env-variable.cmd" Test C:\Temp>echo FOO set to Test. FOO set to Test. PS > $env:FOO Test #> param( ## The path to the script to run [Parameter(Mandatory = $true)] [string] $Path, ## The arguments to the script [string] $ArgumentList ) Set-StrictMode -Version 3 $tempFile = [IO.Path]::GetTempFileName() ## Store the output of cmd.exe. We also ask cmd.exe to output ## the environment table after the batch file completes cmd /c " `"$Path`" $argumentList && set > `"$tempFile`" " ## Go through the environment variables in the temp file. ## For each of them, set the variable in our local environment. Get-Content $tempFile | Foreach-Object { if($_ -match "^(.*?)=(.*)$") { Set-Content "env:\$($matches[1])" $matches[2] } } Remove-Item $tempFile
For more information about running scripts, see Run Programs, Scripts, and Existing Tools.
You want to control how you define (or interact with) the visibility of variables, aliases, functions, and drives.
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
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.
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 Variables, the
*-Variable
set of cmdlets also lets
you specify scope names through their -Scope
parameter.
When working with variables and commands, some concepts feel too minor to deserve an entire new command or function, but the readability 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 (windowTitle becomes $host.UI.RawUI.WindowTitle
).
Note
There are innovative solutions on the Internet that use PowerShell’s debugging facilities to create a breakpoint that changes a variable’s value whenever you attempt to read from it. While unique, this solution causes PowerShell to think that any scripts that rely on the variable are in debugging mode. This, unfortunately, prevents PowerShell from enabling some important performance optimizations in those scripts.
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
resolves this quandary by creating a new variable type
(Dynamic
Variable
) that supports dynamic script actions
when you get or set the variable’s value.
Example 3-4. New-DynamicVariable.ps1
############################################################################## ## ## New-DynamicVariable ## ## From Windows PowerShell Cookbook (O'Reilly) ## by Lee Holmes (http://www.leeholmes.com/guide) ## ############################################################################## <# .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( ## The name for the dynamic variable [Parameter(Mandatory = $true)] $Name, ## The script block to invoke when getting the value of the variable [Parameter(Mandatory = $true)] [ScriptBlock] $Getter, ## The script block to invoke when setting the value of the variable [ScriptBlock] $Setter ) Set-StrictMode -Version 3 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 we've already defined the variable, remove it. if(Test-Path variable:\$name) { Remove-Item variable:\$name -Force } ## Set the new variable, along with its getter and setter. $executioncontext.SessionState.PSVariable.Set( (New-Object Lee.Holmes.DynamicVariable $name,$getter,$setter))
You want to use and interact with one of the features that makes PowerShell so powerful: its intrinsic support for .NET objects.
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
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.”
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
[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
$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.
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. To learn about
the different sets of parameters (overloads) that a given method
supports, type that method name without any parameters:
PS > $now = Get-Date PS > $now.ToString OverloadDefinitions ------------------- string ToString() string ToString(string format) string ToString(System.IFormatProvider provider) string ToString(string format, System.IFormatProvider provider) string IFormattable.ToString(string format, System.IFormatProvider formatProvider) string IConvertible.ToString(System.IFormatProvider provider)
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. In this case, review the
output of the Trace-Command
cmdlet,
with MemberResolution
as the trace
type (see
Example 3-5).
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
If you are adapting a C# example from the Internet and PowerShell can’t find a method used in the example, the method may have been added through a relatively rare technique called explicit interface implementation. If this is the case, you can cast the object to that interface before calling the method:
$sourceObject = 123 $result = ([IConvertible] $sourceObject).ToUint16($null)
[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 they are 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.
$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.
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 for a hand-picked list of the classes in the .NET Framework most useful to system administrators. For an answer to the second, see Learn About Types and Objects and Get Detailed Documentation About Types and Objects.
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.Bitmapsource.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")
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.
Note
To create an instance of a generic object, see Example 3-6.
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 Type
Shortcuts.
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 these
steps:
$startInfo = New-Object Diagnostics.ProcessStartInfo -Property @{ 'Filename' = "powershell.exe"; 'WorkingDirectory' = $pshome; 'Verb' = "RunAs" } [Diagnostics.Process]::Start($startInfo)
Or even more simply through PowerShell’s built-in type conversion:
$startInfo = [Diagnostics.ProcessStartInfo] @{ 'Filename' = "powershell.exe"; 'WorkingDirectory' = $pshome; 'Verb' = "RunAs" }
When calling the New-Object
cmdlet
directly, 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)
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, please see 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. To learn more about the functionality that a class supports, see 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
.
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.
Array
List
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 others.
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 you’re 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 includes 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 2 and on support generic parameters by
placing them between square brackets, as demonstrated in
Example 3-6.
If you are using PowerShell version 1, see New-GenericObject
included in the book’s sample
downloads.
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: "Input 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 (see Example 3-7).
Example 3-7. Creating a multiparameter 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 "System .Int32": "Cannot convert value "Hello" to type "System.Int32". Error: "Input string was not in a correct format."" At line:1 char:9 + $map.Add <<<< ("Test2", "Hello") + CategoryInfo : NotSpecified: (:) [], MethodException + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
You want to reduce the amount of redundant information in your script when you interact with classes that have long type names.
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
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
operator along
with a cast:
$namespace = "System.Diagnostics.{0}" ([Type] ($namespace -f "EventLog"))::GetEventLogs() ([Type] ($namespace -f "Process"))::GetCurrentProcess()
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 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 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
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 Place Formatted Information in a String.
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 -ComObjectProgId
For example:
PS > $sapi = New-Object -Com Sapi.SpVoice PS > $sapi.Speak("Hello World")
Historically, many applications have exposed their scripting and administration interfaces as COM objects. While .NET APIs (and PowerShell cmdlets) are by far the most common, interacting with COM objects is still a routine 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.
For more information about the New-Object
cmdlet, type Get-Help New-Object
.
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
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:
[Type
] | Get-Member -Static Get-Member -InputObject [Type
]
To get members of the specified member type (for example,
Method
or Property
) from an object you have stored in the
$
object
variable, supply that member
type to the -MemberType
parameter:
$object
| Get-Member -MemberTypeMemberType
Get-Member -MemberTypeMemberType
-InputObject $object
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
.
Note
To interactively explore an object’s methods and properties, see Program: Interactively View and Explore Objects.
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 can 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 Get Detailed Documentation About Types and Objects.
For more information about the Get-Member
cmdlet, type Get-Help Get-Member
.
You have a type of object and want to know detailed information about the methods and properties it supports.
The documentation for the .NET Framework [available here] is the best way to get detailed documentation about the methods and properties supported by an object. That exploration generally comes in two stages:
-
Find the type of the object.
To determine the type of an object, you can either use the type name shown by the
Get-Member
cmdlet (as described in Learn About Types and Objects) or call theGetType
() method of an object (if you have an instance of it):PS > $date = Get-Date PS > $date.GetType().ToString() System.DateTime
-
Enter that type name into the search box here.
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 the class’s
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.
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.
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 you can access it from
PowerShell. Static
means that the
property is static (as described in Work with .NET Objects). DateTime
means that the property returns a
DateTime
object when you call it.
means that you can get information from this property but cannot
set the information. Many properties support a get
;
as well (such as
the set
;IsReadOnly
property on
System.IO.FileInfo
), which means that
you can change its value.
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 you can access it
from PowerShell. 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.
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-8.
Example 3-8. A script that adds custom properties to its output of file objects
############################################################################## ## ## Get-OwnerReport ## ## From Windows PowerShell Cookbook (O'Reilly) ## by Lee Holmes (http://www.leeholmes.com/guide) ## ############################################################################## <# .SYNOPSIS Gets a list of files in the current directory, but with their owner added to the resulting objects. .EXAMPLE PS > Get-OwnerReport | Format-Table Name,LastWriteTime,Owner Retrieves all files in the current directory, and displays the Name, LastWriteTime, and Owner #> Set-StrictMode -Version 3 $files = Get-ChildItem foreach($file in $files) { $owner = (Get-Acl $file).Owner $file | Add-Member NoteProperty Owner $owner $file }
For more information about running scripts, see Run Programs, Scripts, and Existing Tools.
The most common type of
information to add to an object is static information in a
NoteProperty
. Add-Member
even uses this as the default if you
omit it:
PS > $item = Get-Item C:\ PS > $item | Add-Member VolumeName "Operating System" PS > $item.VolumeName Operating System
In addition to note properties, 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 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 Create and Initialize Custom Objects.
Although the Add-Member
cmdlet lets
you customize specific objects, it does not let you customize all
objects of that type. For information on how to do that, see
Add Custom Methods and
Properties to Types.
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 (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. To
eliminate the confusion this produced, version 2 of PowerShell
updated the two 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
.
You want to return structured results from a command so that users can easily sort, group, and filter them.
Use the [PSCustomObject]
type cast to a new PSCustomObject
, supplying a hashtable with the
custom information as its value, as shown in Example 3-9.
Example 3-9. Creating a custom object
$output = [PSCustomObject] @{ 'User' = 'DOMAIN\User'; 'Quota' = 100MB; 'ReportDate' = Get-Date; }
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 }
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 afforded 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, as shown in Example 3-10.
Example 3-10. Creating a custom object in PowerShell version 1
$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 1, 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 properties. PowerShell version 2 made this immensely easier
by adding the -Property
parameter to
the New-Object
cmdlet, which applied
to the PSObject
type as well.
PowerShell version 3 made this as simple as possible by directly
supporting the [PSCustomObject]
type
cast.
While creating a PSCustomObject
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 (see
Example 3-11). 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-11. Creating a module designed to be used as a custom object
############################################################################## ## ## PlottingObject.psm1 ## ## From Windows PowerShell Cookbook (O'Reilly) ## by Lee Holmes (http://www.leeholmes.com/guide) ## ############################################################################## <# .SYNOPSIS Demonstrates a module designed to be imported 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 } #> ## Declare some internal variables $SCRIPT:x = 0 $SCRIPT:y = 0 $SCRIPT:angle = 0 $SCRIPT:xScale = -50,50 $SCRIPT:yScale = -50,50 ## And a variable that we will later export $SCRIPT:Points = @() Export-ModuleMember -Variable Points ## A function to rotate the points by a certain amount function Rotate($angle) { $SCRIPT:angle += $angle } Export-ModuleMember -Function Rotate ## A function to move the points by a certain amount function Move($xDelta, $yDelta) { $SCRIPT:x += $xDelta $SCRIPT:y += $yDelta } Export-ModuleMember -Function Move ## A function to draw the given points function Draw { $degToRad = 180 * [Math]::Pi Clear-Host ## Draw the origin PutPixel 0 0 + ## Go through each of the supplied points, ## move them the amount specified, and then rotate them ## by the angle specified 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 ## A helper function to draw a pixel on the screen 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 { ## Take no action on error. We probably just rotated a point ## out of the screen boundary. } }
For more information about creating modules, see Package Common Commands in a Module.
If neither of these options suits 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 Define or Extend a .NET
Class.
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.
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
.
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-12.
Example 3-12. 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 2 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-13. System.Management.Automation.PSDriveInfo
is the
type that the Get-PSDrive
cmdlet
generates.
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-14 adds an AvailableFreeSpace
ScriptProperty
to PSDriveInfo
, and should be placed within the
members section of the template given in
Example 3-13. 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-14. 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>
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-15 adds a Free
AliasProperty
to PSDriveInfo
, and it should also be placed within
the members section of the template given in
Example 3-13. 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.
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,
ScriptMethod
s do not support the
param
statement for parameters.
Example 3-16
adds a Remove
ScriptMethod
to PSDriveInfo
. Like the other additions, place these
customizations within the members section of the template given in
Example 3-13. 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-16. 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>
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 the MSDN documentation.
Use a custom format extension file to define the
formatting for that type, followed by a call to the Update-FormatData
cmdlet to load them into your
session:
$formatFile = Join-Path (Split-Path $profile) "Format.Custom.Ps1Xml" Update-FormatData -PrependPath $typesFile
If a file-based approach is not an option, use the Formats
property of the [Runspace]::DefaultRunspace.InitialSessionState
type to add new formatting definitions for the custom type.
When PowerShell commands produce output, this output comes in the form of richly structured objects rather than basic streams of text. These richly structured objects stop being of any use once they make it to the screen, though, so PowerShell guides them through one last stage before showing them on screen: formatting and output.
The formatting and
output system is based on the concept of views. Views can take several forms:
table views, list views, complex views, and more. The most common
view type is a table view. This is the form you see when you use
Format-Table
in a command, or when an
object has four or fewer properties.
As with the custom type extensions described in Add Custom Methods and Properties to Types, PowerShell supports both file-based and in-memory updates of type formatting definitions.
The simplest and most common way to define formatting for a type
is through the Update-FormatData
cmdlet, as shown in the Solution. The Update-FormatData
cmdlet takes paths to
Format.ps1xml files as
input. There are many examples of formatting definitions in the
PowerShell installation directory that you can use. To create your
own formatting customizations, use these files as a source of
examples, but do not modify them directly. Instead, create a new
file and use the Update-FormatData
cmdlet to load your customizations.
For more information about the features supported by these
formatting XML files, type Get-Help
about_format.ps1xml
.
In addition to file-based formatting, PowerShell makes it possible (although not easy) to create formatting definitions from scratch. Example 3-17 provides a script to simplify this process.
Example 3-17. Add-FormatData.ps1
############################################################################## ## ## Add-FormatData ## ## From Windows PowerShell Cookbook (O'Reilly) ## by Lee Holmes (http://www.leeholmes.com/guide) ## ############################################################################## <# .SYNOPSIS Adds a table formatting definition for the specified type name. .EXAMPLE PS > $r = [PSCustomObject] @{ Name = "Lee"; Phone = "555-1212"; SSN = "123-12-1212" } PS > $r.PSTypeNames.Add("AddressRecord") PS > Add-FormatData -TypeName AddressRecord -TableColumns Name, Phone PS > $r Name Phone ---- ----- Lee 555-1212 #> param( ## The type name (or PSTypeName) that the table definition should ## apply to. $TypeName, ## The columns to be displayed by default [string[]] $TableColumns ) Set-StrictMode -Version 3 ## Define the columns within a table control row $rowDefinition = New-Object Management.Automation.TableControlRow ## Create left-aligned columns for each provided column name foreach($column in $TableColumns) { $rowDefinition.Columns.Add( (New-Object Management.Automation.TableControlColumn "Left", (New-Object Management.Automation.DisplayEntry $column,"Property"))) } $tableControl = New-Object Management.Automation.TableControl $tableControl.Rows.Add($rowDefinition) ## And then assign the table control to a new format view, ## which we then add to an extended type definition. Define this view for the ## supplied custom type name. $formatViewDefinition = New-Object Management.Automation.FormatViewDefinition "TableView",$tableControl $extendedTypeDefinition = New-Object Management.Automation.ExtendedTypeDefinition $TypeName $extendedTypeDefinition.FormatViewDefinition.Add($formatViewDefinition) ## Add the definition to the session, and refresh the format data [Runspace]::DefaultRunspace.InitialSessionState.Formats.Add($extendedTypeDefinition) Update-FormatData