powershell reading notes

【声明】本文为AdamsLee原创,转载请注明出自围炉网并保留本文有效链接:powershell reading notes, 转载请保留本声明!

Another, less frequently used way of doing this is by using the special endof-

parameters parameter which is two hyphens back to back, as in “ <span style="font-size:9pt"–<”.

Everything after this sequence will be treated as an argument, even if it

looks like a parameter. For example, using “ <span style="font-size:9pt"–<” we could also write out the

string -inputobject without using quotes by doing:

PS (3) > write-output — -inputobject





The “<span style="font-size:9pt"–<” sequence tells the parameter binder to treat everything after it as

an argument, even if it looks like a parameter. This is a convention adopted

from the UNIX shells and is standardized in the POSIX Shell and Utilities


there are four categories of commands in PowerShell:

cmdlets, functions, scripts, and native Win32 executables.

The first category of command is a cmdlet (pronounced “command-let” ). Cmdlet

is a term that’s specific to the PowerShell environment. A cmdlet is implemented by a

.NET class that derives from the Cmdlet base class in the PowerShell Software Developers

Kit (SDK).

The next type of command is a function. This is a named piece of PowerShell script

code that lives in memory while the interpreter is running, and is discarded on exit.

(See chapter 7 for more information on how you can load functions into your environment.)

Functions are made up of user-defined code that is parsed once when

defined. This parsed representation is preserved so it doesn’t have to be reparsed every

time it is used. Functions can have named parameters like cmdlets, but don’t have the

full parameter specification capabilities of cmdlets in the first version of PowerShell.

Notice, though, that the same basic structure is followed. The section in the script that

begins with the process keyword (line 4 of listing 2.2) corresponds to the ProcessRecord

method shown in listing 2.1. This allows functions and cmdlets to have

the same streaming behavior.

A script command is a piece of PowerShell code that lives in a file with a .ps1 extension.

In version 1.0 of PowerShell, these script files are loaded and parsed every time

they are run, making them somewhat slower than functions to start (although once

started, they run at the same speed). In terms of parameter capabilities, shell function

commands and script commands are identical.

One final issue you may be wondering about: what is the $OFS (Output Field Separator) variable doing in the example? When PowerShell converts arrays to strings, it takes each array element, converts that element into a string, and then concatenates all the pieces together. Since this would be an unreadable mess, it inserts a separator between each element. That separator is specified using the $OFS variable. It can be set to anything you want, even the empty string. Here’s an interesting example. Say we want to add the numbers from 1 to 10. Let’s put the numbers into an array: PS (1) > $data = 1,2,3,4,5,6,7,8,9,10 Now convert them to a string: PS (2) > [string] $data 1 2 3 4 5 6 7 8 9 10 As an aside, variable expansion in strings goes through the same mechanism as the type converter, so you’ll get the same result: PS (3) > "$data" 1 2 3 4 5 6 7 8 9 10 Now change $OFS to be the plus operator (“+”), and then display the data. PS (4) > $OFS='+' PS (5) > "$data" 1+2+3+4+5+6+7+8+9+10 Previously, the fields had been separated by spaces. Now they’re separated by plus operators. This is almost what we need. We just have to find a way to execute this string. PowerShell provides ability through the Invoke-Expression cmdlet. Here’s how it works. PS (6) > invoke-expression "$data" 55 PS (7) > Ta-da! Note that this is not an efficient way to add a bunch of numbers. The looping language constructs are a much better way of doing this.

the notation ${c:old.txt} says: return the contents of the file “old.txt” from the current working directory on the C: drive. In contrast, ${c:\old.txt} says get the file “old.txt” from the root of the C: drive. ${c:old.txt} -replace 'is (red|blue)','was $1' > new.txt

The “left-hand” rule for arithmetic operators: The type of the left-hand operand determines the type of the overall operation. This is an important rule to remember. If the left operand is a number, PowerShell will try to convert the right operand to a number. Here’s an example. In the following expression, the operand on the left is a number and the operand on the right is the string “123”. PS (1) > 2 + "123" 125

Here is an important characteristic about how division works in PowerShell that you should keep in mind. Integer division underflows into floating point (technically System.Double). This means that 5 divided by 4 in PowerShell results in 1.25 instead of 1 as it would in C#. If you want to round the decimal part to the nearest integer, simply cast the result into [int]. You also need to be aware that PowerShell uses what’s called “Banker’s Rounding” when converting floating point numbers into integers. Banker’s rounding rounds .5 up sometimes, and down sometimes. The convention is to round to the nearest even number, so that both 1.5 and 2.5 round to 2, and 3.5 and 4.5 both round to 

Here’s a quick example where multiple assignment is particularly useful. The canonical pattern for swapping two variables is conventional languages is $temp = $a $a = $b $b = $temp This takes three lines of code and requires you to use a temporary variable. Here’s how to do it using multiple assignments in PowerShell: $a,$b = $b,$a

First let’s look at the data file:quiet 0 25normal 26 50loud 51 75noisy 75 100This file contains a set of sound level descriptions. The format is a string describingthe level, followed by two numbers describing the upper and lower bounds for theselevels out of a possible 100. We want to read this information into a data structure sowe can use it to categorize a list of sounds later on. Here’s the fragment of PowerShellcode needed to do this:PS (2) > $data = get-content data.txt | foreach {>> $e=@{}>> $e.level, [int] $e.lower, [int] $e.upper = $_.split()>> $e>> }>>

Let’s talk about the most contentious design decision in the PowerShell language. And the winner is: why the heck did we not use the conventional symbols for comparison like “>”, “>=”, “<”, “<=”, “==”, and “!=” ? My, this was a touchy issue. The answer is that the “>” and “<” characters are used for output redirection. Since PowerShell is a shell and all shell languages in the last 30 years have used “>” and “<” for I/O redirection, people expected that PowerShell should do the same. During the first public beta of PowerShell, this topic generated discussions that went on for months. We looked at a variety of alternatives, such as modal parsing where sometimes “>” meant greater-than and sometimes it meant redirection. We looked at alternative character sequences for the operators like “:>” or “->”, either for redirection or comparison. We did usability tests and held focus groups, and in the end, settled on what we had started with. The redirection operators are “>” and “<”, and the comparison operators are taken from the UNIX test(1) command. We expect that, since these operators have a 30year pedigree, they are adequate and appropriate to use in PowerShell. (We also expect that people will continue to complain about this decision, though hopefully not for 30 more years.

There are two forms of the subexpression construct, as shown in the following: $( <statementList> ) @( <statementList> ) The syntactic difference between a subexpression (either form) and a simple parenthetical expression is that you can have any list of statements in a subexpression instead of being restricted to a single pipeline. This means that you can have any PowerShell language element in these grouping constructs, including loop statements. It also means that you can have several statements in the group. Let’s look at an example. Earlier in this chapter, we looked at a short piece of PowerShell code that calculates the numbers in the Fibonacci sequence below 100. At the time, we didn’tcount the number of elements in that sequence. We can do this easily using the sub-expression grouping construct.PS (1) > $($c=$p=1; while ($c -lt 100) {$c; $c,$p=($c+$p),$c}).count10By enclosing the statements in $( … ), we can retrieve the result of the enclosedcollection of statements as an array

Now let’s take a look at the difference between the array subexpression @( … ) and the regular subexpression. The difference is that in the case of the array subexpression, the result is always returned as an array; this is a fairly small but very useful difference. In effect, it’s shorthand for: [object[]] $( … ) This shorthand exists because in many cases you don’t know if a pipeline operation is going to return a single element or a collection. Rather than writing complex checks, you can use this construction and be assured that the result will always be a collection. If the pipeline returns an array, no new array is created. If, however, the pipeline returns a scalar value, that value will be wrapped in a single element. (Note that this is not the same as the comma operator, which always wraps its argument value in a new one-element array

The comma operator has higher precedence than any other operator exceptcasts and property and array references. This is worth calling out again be-cause it’s important to keep in mind when writing expressions. If you don’tremember this, you will produce some strange results

You can also write to a file using the namespace variable notation. Here’s that example rewritten to use variable assignment instead of a redirection operator (remember, earlier we said that assignment can be considered a form of redirection in PowerShell.) ${c:new.txt} = ${c:old.txt} -replace 'is (red|blue)','was $1' In fact, you can even do an in-place update of a file by using the same variable on both sides of the assignment operator. To update the file “old.txt” instead of making a copy, do ${c:old.txt} = ${c:old.txt} -replace 'is (red|blue)','was $1' All we did was change the name in the variable reference from “new.txt” to “old.txt”. This won’t work if you use the redirection operator, because the output file is opened before the input file is read

When accessing a file using the variable namespace notation, PowerShell assumes that it’s working with a text file. Since the notation doesn’t provide a mechanism for specifying the encoding, you can’t use this technique on binary files. You’ll have to use the Get-Content and Set-Content cmdlets instead

What happens if we don’t want a default value? In other words, how can we require the user to specify this value? This is another place where you can use initializer expressions. Since the expression is can do anything, it can also generate an error. Here’s how you can use the throw statement to generate an error (we’ll cover the throw statement in detail in chapter 9). First we define the function. PS (31) > function zed ($x=$(throw "need x")) { "x is $x" } Notice how the throw statement is used in the initializer subexpression for $x.

the only syntactic difference between a function and a filter is the keyword. The significant differences are all semantic. A function runs once and runs to completion. When used in a pipeline, it halts streaming—the previous element in the pipeline runs to completion; only then does the function begin to run. It also has a special variable $input defined when used as anything other than the first element of the pipeline. By contrast, a filter is run once and to completion for each element in the pipeline. Instead of the variable $input, it has a special variable $_ that contains the current pipeline object

When exit is used in a script, it exits that script. This is true even when called from a function in that script

The exit statement is also how we set the exit code for the PowerShell process when calling PowerShell.exe from another program

To execute a script with a space in the name, we need to do the same thing we’d do at the PowerShell command prompt: put the name in quotes and use the call (&) operator: PowerShell.exe "& './my script.ps1'" 

What follows the . isn’t limited to a simple file name. It could be a variable or expression, as was the case with “&”: PS (12) > $name = "./my-script" PS (13) > . $name Setting x to 22 The last thing to note is that dotting works for both scripts and functions

What we call scriptblocks in PowerShell are called anonymous functions or sometimes lambda expressions in other languages. The term lambda comes from the lambda calculus developed by Alonzo Church and Stephen Cole Kleene in the 1930s. A number of languages, including Python and dialects of LISP, still use lambda as a language keyword. In designing the PowerShell language, we felt that calling a spade and spade (and a scriptblock a scriptblock) was more straightforward (the coolness of using Greek letters aside

So you have to use the call operator “&”. If, for instance, you have a command called “my command”, you would invoke this command by typing the following: & "my command" The interpreter sees the call operator and uses the value of the next argument to look up the command to run. This process of looking up the command is called command discovery. The result of this command discovery operation is an object of type System.Management.Automation.CommandInfo, which tells the interpreter what command to execute

What we really need is a way to attach new members to a type, rather than through an instance. PowerShell does this through type configuration files. These configuration files are stored in the installation directory for PowerShell and loaded at startup. The installation directory path for PowerShell is stored in the $PSHome variable, so it’s easy to find these files. They have the word “type” in their names and have an extension .ps1xml

PowerShell provides a mechanism for directly executing a string without first building a scriptblock. This is done with the Invoke-Expression cmdlet

There is a special variable $error that contains a collection of the errors that occurred. This is maintained as a circular bounded buffer. As new errors occur, old ones are discarded. The number of errors that are retained is controlled by the $MaximumErrorCount variable. The collection in $error is an array (technically an instance of System.Collections.ArrayList) that buffers errors as they occur. The most recent error is always stored in $error[0]. 

The example here uses 1/$null. The reason for doing this instead of simply 1/0 is because the PowerShell interpreter does something called constant expression folding. It looks at expressions that contain only constant values. When it sees one, it evaluates that expression once at compile time so it doesn’t have to waste time doing it again at runtime. This means that impossible expressions, such as division by zero, are caught and treated as parsing errors. Parsing errors can’t be caught and don’t get logged when they’re entered interactively, so they don’t make for a good example. (If one script calls another script and that script has one of these errors, the calling script can catch it, but the script being parsed cannot

when you want to capture all the errors from a specific command, you use a standard parameter on all commands called -ErrorVariable. This parameter names a variable to use for capturing all the errors that the command generates


In this example, the argument to -ErrorVariable is specified as a string with no leading $. If it had instead been written as $errs then the errors would have been stored in the variable named by the value in $errs, not $errs itself. Also note that the -ErrorVariable parameter works like a tee; i.e., the objects are captured in the variable, but they are also streamed to the error output

The $? variable will be true if the entire operation succeeded, and false otherwise. If any of the operations generated an error object, then $? will be set to false. This is an important point. It means that a script can determine whether an error occurred even if the error is not displayed

Where the $? variable only indicates success or failure, $LASTEXITCODE contains the exit code of the last command run. This, however, only applies to two types of commands: native or external commands and PowerShell scripts. On Windows, when a process exits, it can return a single integer as its exit code. This integer is used to encode a variety of different conditions, but the only one we’re interested in is whether it’s zero or non-zero. This is a convention that is used by almost all programs. If they were successful then their exit code is zero. If they encountered an error then the exit code will be non-zero. PowerShell captures this exit code in $LASTEXITCODE, and if that value is non-zero then it sets $? to false

There are two ways to set the error action preference: by setting the variable $ErrorActionPreference as in $ErrorActionPreference = “silentlycontinue” or by using the -erroraction (or -ea) parameter that is available on all cmdlets

note the use of the call operator ‘&’ with a scriptblock containing the scope for the preference setting. Using the pattern & { } … script code… you can execute fragments of script code so that any variables set in the script are discarded at the end of the scriptblock. Because setting $ErrorActionPreference has such a profound effect on the execution of the script, we’re using this technique to isolate the preference setting

The exception that was trapped is available in the trap block in the $_ variable. However, $_ is not an exception; it’s an error record, so the trap handler has full access to all of the information in the error handler.

Exceptions are caught using the trap statement. This is a statement that can appear anywhere in a block of code. When an exception (terminating error) occurs that is not otherwise handled, control will be transferred to the body of the trap statement. The body of the trap statement is then executed

With .NET, Microsoft tried to solve some of these problems. An assembly is a DLL with additional data in the form of a manifest. This manifest lists the contents of the DLL as well as the name of the DLL. Assembly names are particularly interesting. .NET introduced the idea of a strong name. A strong name uses public key cryptography to verify the author of the DLL. When a .NET program is linked against a strongnamed assembly, it will run only if exactly the same assembly it was linked against is present. Simply replacing the file won’t work, because the strong name will be wrong. Included in the strong name is the version number. This means that when the DLL is loaded, the correct version will always be loaded even if later versions are available

Linking and strong naming only apply to compiled .NET programs and PowerShell is an interpreter

Another thing to keep in mind is that when using any .NET method that takes path names, we must always use full path names. This requirement stems from the fact that PowerShell maintains its own idea of what the current working directory is and this may not be the same as the process current working directory