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.
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).
'System.Diagnostics. Process' should be 'System.Diagnostics.Process' - without a space.
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
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:
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.
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 powershellIn
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.
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 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 -AutoThe 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.
Bad font of '*.format.ps1xml'
Thanks. That's actually the font for "replacable" text, so is correct by O'Reilly standards.
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.
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”.
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 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 IdleVariables 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”.
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"
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.
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.
assigni => assign
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.
Get-Help
about_automatic_variables
You want to use an environment variable (such as the system path, or 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:variablenamei.e.:
$env:username
To get an environment variable using its
Provider path, supply env: or Environment:: to the Get-ChildItem cmdlet:
Get-ChildItem env:variablenameGet-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: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:\WINDOWSHave other version of more function. Maybe this one is from v1? (can't check now)
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).
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:\WINDOWSThis variable syntax for content management lets you to both get and set content:
PS > $function:more = { $input | less.exe }
PS > $function:more
$input | less.exeNow, 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 worldfor some reason the t's are being formatted as tabs here
Thanks. It looks like this is a bug in the review system. It appears correctly in the manuscript and the RSS feed :)
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.
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.
A couple spaces missing: should be "Invoke-CmdScript Scriptname.cmd or Invoke-CmdScript Scriptname.bat"
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”.
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 = valueTo access a variable at a specific scope, supply that scope before the variable name:
$SCOPE:variableTo create a variable that remains even after
the script exits, create it in the GLOBAL scope:
$GLOBAL:variable = valueTo change a scriptwide variable from within a
function, supply SCRIPT as its scope
name:
$SCRIPT:variable = valuePowerShell 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.
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:
The outermost scope. Items in the global scope are visible from all other scopes.
The scope that represents the current script. Items in the script scope are visible from all other scopes in the script.
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 = valueAs illustrated in the section called “Variables”, the *-Variable set of cmdlets also let you
specify scope names through their -Scope parameter.
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:MyFunctionargs
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.
Great catch! Thanks.
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.
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))
You want to use and interact with one of the features that make 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.PropertyNameOne 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.
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.
Stores notepad process into the $process variable.
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[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.
$objectReference.PropertyNameor
$objectReference.PropertyName = valueLike 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.
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.
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”.
You want to create an instance of a .NET object to interact with its methods and properties.
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.
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”.
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)
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.
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 : MethodArgumentConversionInvalidCastArgumentFor 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 : MethodArgumentConversionInvalidCastArgumentPowerShell 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:
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.
$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)
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
(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()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”.
You want to create a COM object to interact with its methods and properties.
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 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.
You have an instance of an object and want to know what methods and properties it supports.
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
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:
space before '-Static'
[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 -MemberTypeMemberTypeGet-Member -MemberTypememberType-InputObject $object
$object | Get-Member -MemberType memberType
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.
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 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:
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
Enter that type name into the search box at http://msdn.microsoft.com.
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.
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.
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 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.
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.
You have an object and want to add your own custom properties or methods (members) to that object.
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
}
It shows standard properties. Should be: $file | select Name, Owner
Thanks. The second line in the example illustrates how to show the owner.
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.
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
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).
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.
You want to return structured results from a command so that users can easily sort, group, and filter them.
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 $outputIf 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 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.
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”.
You want to add your own custom properties or methods to all objects of a certain type.
Use custom type extension files to add custom members to all objects of a type.
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.
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.
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.
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>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>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>
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>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.
1 comment
I will review this chapter
Add a comment