Skip to main content

Windows

Internet-Enabled Scripts

Although PowerShell provides an enormous benefit even when your scripts interact only with the local system, working with data sources from the Internet opens exciting and unique opportunities. For example, you might download files or information from the Internet, interact with a web service, store your output as HTML, or even send an email that reports the results of a longrunning script.

Through its cmdlets and access to the networking support in the .NET Framework, PowerShell provides ample opportunities for Internetenabled administration.

Files and Directories of Windows PowerShell

One of the most common tasks when administering a system is working with its files and directories. This is true when you administer the computer at the command line, and it is true when you write scripts to administer it automatically.

Fortunately, PowerShell makes scripting files and directories as easy as working at the command line—a point that many seasoned programmers and scripters often miss. Aperfect example of this comes when you wrestle with limited disk space and need to find the files taking up the most space.

Atypical programmer might approach this task by writing functions to scan a specific directory of a system. For each file, they check whether the file is big enough to care about. If so, they add it to a list. For each directory in the original directory, the programmer repeats this process (until there are no more directories to process).

As the saying goes, though, “you can write C in any programming language.” The habits and preconceptions you bring to a language often directly influence how open you are to advances in that language.

Being an administrative shell, PowerShell directly supports tasks such as visiting all the files in a subdirectory or moving a file from one directory to another. That complicated programmeroriented script turns into a oneliner:

GetChildItem –Recurse | SortObject Descending Length | Select First 10

Before diving into your favorite programmer’s toolkit, check to see what PowerShell supports in that area. In many cases, it can handle it without requiring your programmer’s bag of tricks.

Search for a Computer Account

Problem

You want to search for a specific computer account, but don’t know its DN.

Solution

To search for a computer account, use the [adsi] type shortcut to bind to a container that holds the account in Active Directory, and then use the System. DirectoryServices.DirectorySearcher class from the .NET Framework to search for the account:

$domain = [adsi] "LDAP://localhost:389/dc=Fabrikam,dc=COM" $searcher = NewObject System.DirectoryServices.DirectorySearcher $domain $searcher.Filter = '(&(objectClass=Computer)(name=kenmyer_laptop))'

$computerResult = $searcher.FindOne() $computer = $computerResult.GetDirectoryEntry()

Discussion

When you don’t know the full DN of a computer account, the System. DirectoryServices.DirectorySearcher class from the .NET Framework lets you search for it.

You provide an LDAP filter (in this case, searching for computers with the name of kenmyer_laptop), and then call the FindOne() method. The FindOnel() method returns the first search result that matches the filter, so we retrieve its actual Active Directory entry. Although the solution searches on the computer’s name, you can search on any field in Active Directory—the sAMAccountName and operating system characteristics (operatingSystem, operatingSystemVersion, operatingSystemServicePack) are other good choices.

When you do this search, always try to restrict it to the lowest level of the domain possible. If you know that the computer is in the Sales OU, it would be better to bind to that OU instead:

$domain = [adsi] "LDAP://localhost:389/ou=Sales,dc=Fabrikam,dc=COM"

For more information about the LDAP search filter syntax, search http://msdn. microsoft.com for “Search Filter Syntax.”

Perform Complex Arithmetic in Windows PowerShell

Problem

You want to use PowerShell to calculate more complex or advanced mathematical results.

Solution

PowerShell supports more advanced mathematical tasks primarily through its sup port for the System.Math class in the .NET Framework. To find the absolute value of a number, use the [Math]::Abs() method:

PS >[Math]::Abs(10.6)

10.6

To find the power (such as the square or the cube) of a number, use the [Math]:: Pow() method. In this case, finding 123 squared:

PS >[Math]::Pow(123, 2)

15129 To find the square root of a number, use the [Math]::Sqrt() method:

PS >[Math]::Sqrt(100)

10 To find the sine, cosine, or tangent of an angle (given in radians), use the [Math]:: Sin(), [Math]::Cos(), or [Math]::Tan() method:

PS >[Math]::Sin( [Math]::PI / 2 ) 1

To find the angle (given in radians) of a sine, cosine, or tangent value, use the [Math]::ASin(), [Math]::ACos(), or [Math]::ATan() method:

PS >[Math]::ASin(1)

1.5707963267949

Discussion

Once you start working with the System.Math class, it may seem as though its designers left out significant pieces of functionality. The class supports the square root of a number, but doesn’t support other roots (such as the cube root). It supports sine, cosine, and tangent (and their inverses) in radians, but not in the more commonly used measure of degrees.

Working with any root

To determine any root (such as the cube root) of a number, you can use the function given in Example 61.

Example 61. A root function and some example calculations

PS >function root($number, $root) { [Math]::Exp($([Math]::Log($number) / $root)) } PS >root 64 3 4 PS >root 25 5 1.90365393871588 PS >[Math]::Pow(1.90365393871588, 5) 25.0000000000001 PS >[Math]::Pow( $(root 25 5), 5) 25

This function applies the mathematical fact that you can express any root (such as the cube root) of a number as a series of operations with any other wellknown root. Although you can pick 2 as a base (which generates square roots and powers of 2), the [Math]::Exp() and [Math]::Log() methods in the System.Math class use a more common mathematical base called e.

The [Math]::Exp() and [Math]::Log() functions use e (approximately 2.718) as a base largely because the number appears so frequently in the type of mathematics that use these functions!

The example also illustrates a very important point about math on computers. When you use this function (or anything else that manipulates floating point numbers), always be aware that the results of floating point answers are only ever approximations of the actual result. If you combine multiple calculations in the same statement, programming and scripting languages can sometimes improve the accuracy of their answer (such as in the second [Math]::Pow() attempt), but that exception is rare.

Some mathematical systems avoid this problem by working with equations and calculations as symbols (and not numbers). Like humans, these systems know that taking the square of a number that you just took the square root of gives you the original number right back—so they don’t actually have to do either of those operations. These systems, however, are extremely specialized and usually very expensive.

Working with degrees instead of radians

Converting radians (the way that mathematicians commonly measure angles) to degrees (the way that most people commonly measure angles) is much more straightforward than the root function. Acircle has 2*Pi radians if you measure in radians, and 360 degrees if you measure in degrees. That gives the following two functions:

PS >function ConvertRadiansToDegrees($angle) { $angle / (2 * [Math]::Pi) * 360 } PS >function ConvertDegreesToRadians($angle) { $angle / 360 * (2 * [Math]::Pi) }

and their usage:

PS >ConvertRadiansToDegrees ([Math]::Pi) 180 PS >ConvertRadiansToDegrees ([Math]::Pi / 2) 90 PS >ConvertDegreesToRadians 360 6.28318530717959 PS >ConvertDegreesToRadians 45 0.785398163397448 PS >[Math]::Tan( (ConvertDegreesToRadians 45) ) 1

Perform Simple Arithmetic in Windows PowerShell

Problem

You want to use PowerShell to calculate simple mathematical results.

Solution

Use PowerShell’s arithmetic operators:

+
Addition

-
Subtraction

*
Multiplication

/
Division

%
Modulus

+=, -=, *=, /=, and %= Assignment variations of the above ( ) Precedence/Order of operations

Discussion

One difficulty in many programming languages comes from the way that they handle data in variables. For example, this C# snippet stores the value of “1” in the result variable, when the user probably wanted the result to hold the floating point value of 1.5:

double result = 0; result = 3/2;

This is because C# (along with many other languages) determines the result of the division from the type of data being used in the division. In the example above, it decides that you want the answer to be an integer since you used two integers in the division.

PowerShell, on the other hand, avoids this problem. Even if you use two integers in a division, PowerShell returns the result as a floating point number if required. This is called widening.

PS >$result = 0 PS >$result = 3/2 PS >$result

1.5

One exception to this automatic widening is when you explicitly tell PowerShell the type of result you want. For example, you might use an integer cast ([int]) to say that you want the result to be an integer after all:

PS >$result = [int] (3/2) PS >$result 2

Many programming languages drop the portion after the decimal point when they convert them from floating point numbers to integers. This is called truncation. PowerShell, on the other hand, uses banker’s rounding for this conversion. It converts floating point numbers to their nearest integer, rounding to the nearest even number in case of a tie.

Several programming techniques use truncation, though, so it is still important that a scripting language somehow support it. PowerShell does not have a builtin operator that performs a truncationstyle division, but it does support it through the [Math]:: Truncate() method in the .NET Framework:

PS >$result = 3/2 PS >[Math]::Truncate($result) 1

If that syntax seems burdensome, the following example defines a trunc function that truncates its input:

PS >function trunc($number) { [Math]::Truncate($number) } PS >$result = 3/2 PS >trunc $result 1

Program: Invoke Native Windows API Calls

There are times when neither PowerShell’s cmdlets nor scripting language directly support a feature you need. In most of those situations, PowerShell’s direct support for the .NET Framework provides another avenue to let you accomplish your task. In some cases, though, even the .NET Framework does not support a feature you need to resolve a problem, and the only way to resolve your problem is to access the core Windows APIs.

For complex API calls (ones that take highly structured data), the solution is to write a PowerShell cmdlet that uses the P/Invoke (Platform Invoke) support in the .NET Framework. The P/Invoke support in the .NET Framework lets you access core Windows APIs directly.

Although it is possible to determine these P/Invoke definitions yourself, it is usually easiest to build on the work of others. If you want to know how to call a specific Windows API from a .NET language, the http://pinvoke.net web site is the best place to start.

If the API you need to access is straightforward (one that takes and returns only sim ple data types), however, Example 157 lets you call these Windows APIs directly from PowerShell.

Example 157. InvokeWindowsApi.ps1

############################################################################## ## ## InvokeWindowsApi.ps1 ## ## Invoke a native Windows API call that takes and returns simple data types. ## ## ie: ## ## ## Prepare the parameter types and parameters for the ## CreateHardLink function ## $parameterTypes = [string], [string], [IntPtr] ## $parameters = [string] $filename, [string] $existingFilename, [IntPtr]::Zero ##

Example 157. InvokeWindowsApi.ps1 (continued)

## ## Call the CreateHardLink method in the Kernel32 DLL ## $result = InvokeWindowsApi "kernel32" ([bool]) "CreateHardLink" ` ## $parameterTypes $parameters ## ############################################################################## param(

[string] $dllName, [Type] $returnType, [string] $methodName, [Type[]] $parameterTypes, [Object[]] $parameters )

## Begin to build the dynamic assembly $domain = [AppDomain]::CurrentDomain $name = NewObject Reflection.AssemblyName 'PInvokeAssembly' $assembly = $domain.DefineDynamicAssembly($name, 'Run') $module = $assembly.DefineDynamicModule('PInvokeModule') $type = $module.DefineType('PInvokeType', "Public,BeforeFieldInit")

## Go through all of the parameters passed to us. As we do this, ## we clone the user's inputs into another array that we will use for ## the P/Invoke call. $inputParameters = @() $refParameters = @()

for($counter = 1; $counter le $parameterTypes.Length; $counter++)

{ ## If an item is a PSReference, then the user ## wants an [out] parameter. if($parameterTypes[$counter 1] eq [Ref]) {

## Remember which parameters are used for [Out] parameters $refParameters += $counter

## On the cloned array, we replace the PSReference type with the ## .Net reference type that represents the value of the PSReference, ## and the value with the value held by the PSReference. $parameterTypes[$counter 1] =

$parameters[$counter 1].Value.GetType().MakeByRefType()

$inputParameters += $parameters[$counter 1].Value } else {

## Otherwise, just add their actual parameter to the ## input array. $inputParameters += $parameters[$counter 1]

} }

## Define the actual P/Invoke method, adding the [Out] ## attribute for any parameters that were originally [Ref]

Example 157. InvokeWindowsApi.ps1 (continued)

## parameters. $method = $type.DefineMethod($methodName, 'Public,HideBySig,Static,PinvokeImpl',

$returnType, $parameterTypes) foreach($refParameter in $refParameters) {

[void] $method.DefineParameter($refParameter, "Out", $null) }

## Apply the P/Invoke constructor $ctor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([string]) $attr = NewObject Reflection.Emit.CustomAttributeBuilder $ctor, $dllName $method.SetCustomAttribute($attr)

## Create the temporary type, and invoke the method. $realType = $type.CreateType()

$realType.InvokeMember($methodName, 'Public,Static,InvokeMethod', $null, $null, $inputParameters)

## Finally, go through all of the reference parameters, and update the ## values of the PSReference objects that the user passed in. foreach($refParameter in $refParameters) {

$parameters[$refParameter 1].Value = $inputParameters[$refParameter 1] }

Modify Properties of an Organizational Unit in PowerShell

Problem

You want to modify properties of a specific OU.

Solution

To modify the properties of an OU, use the [adsi] type shortcut to bind to the OU in Active Directory, and then call the Put() method. Finally, call the SetInfo() method to apply the changes.

$organizationalUnit = [adsi] "LDAP://localhost:389/ou=West,ou=Sales,dc=Fabrikam,dc=COM"

$organizationalUnit.Put("Description", "Sales West Organization") $organizationalUnit.SetInfo()

Discussion

The solution retrieves the Sales West OU. It then sets the description to Sales West Organization, and then applies those changes to Active Directory.

Add Custom Methods and Properties to Types

Problem

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

Solution

Use custom type extension files to add custom members to all objects of a type.

Discussion

Although the AddMember 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 allow you to 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.

Getting started

If you haven’t already, the first step in creating a types extension file is to create an empty one. The best location for this is probably in the same directory as your custom profile, with the name Types.Custom.ps1xml,

Example 35. Sample Types.Custom.ps1xml file

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

$typeFile = (JoinPath (SplitPath $profile) "Types.Custom.ps1xml")

UpdateTypeData PrependPath $typeFile By default, PowerShell loads several type extensions from the Types.ps1xml file in PowerShell’s installation directory. The UpdateTypeData cmdlet tells PowerShell to also look in your Types.Custom.ps1xml file for extensions. The PrependPath parameter makes PowerShell favor your extensions over the builtin 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.

To support this, you need to extend your custom types file so that it defines additions to the System.Management.Automation.PSDriveInfo type, System.Management.Automation.PSDriveInfo type is the type that the GetPsDrive cmdlet generates.

Example 36. A template for changes to a custom types file

System.Management.Automation.PSDriveInfo

add members such as

here

Add a ScriptProperty

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

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

AvailableFreeSpace ScriptProperty to PSDriveInfo, and should 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 37. A ScriptProperty for the PSDriveInfo type

AvailableFreeSpace

## 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 = NewObject System.IO.DriveInfo $driveRoot $drive.AvailableFreeSpace }

}

## 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." WriteHost $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, " +

Example 37. A ScriptProperty for the PSDriveInfo type (continued)

" use up $spaceDifference more megabytes." WriteHost $message }

Add an AliasProperty

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

Free AliasProperty to PSDriveInfo, and should also be placed the property, it returns the value of the AvailableFreeSpace property. When you set the property, it sets the value of the AvailableFreeSpace property.

Example 38. An AliasProperty for the PSDriveInfo type

Free

AvailableFreeSpace

Add a ScriptMethod

A ScriptMethod allows you to 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.

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

Example 39. A ScriptMethod for the PSDriveInfo type

Remove

Add other extension points

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

Looping and Flow Control in Windows PowerShell

As you begin to write scripts or commands that interact with unknown data, the concepts of looping and flow control become increasingly important.

PowerShell’s looping statements and commands let you perform an operation (or set of operations) without having to repeat the commands themselves. This includes, for example, doing something a specified number of times, processing each item in a collection, or working until a certain condition comes to pass.

PowerShell’s flow control and comparison statements let you to adapt your script or command to unknown data. They let you execute commands based on the value of that data, skip commands based on the value of that data, and more.

Together, looping and flow control statements add significant versatility to your PowerShell toolbox.

Find Your Script’s Name in PowerShell

Problem

You want to know the name of the currently running script.

Solution

To determine the full path and filename of the currently executing script, use this function:

function GetScriptName

{

$myInvocation.ScriptName

}

To determine the name that the user actually typed to invoke your script (for example, in a “Usage” message), use the $myInvocation.InvocationName variable.

Discussion

By placing the $myInvocation.ScriptName statement in a function, we drastically simplify the logic it takes to determine the name of the currently running script. If you don’t want to use a function, you can invoke a script block directly, which also simplifies the logic required to determine the current script’s name:

$scriptName = & { $myInvocation.ScriptName }

Although this is a fairly complex way to get access to the current script’s name, the alternative is a bit more errorprone. If you are in the body of a script, you can directly get the name of the current script by typing:

$myInvocation.Path

If you are in a function or script block, though, you must use:

$myInvocation.ScriptName

Working with the $myInvocation.InvocationName variable is sometimes tricky, as it returns the script name when called directly in the script, but not when called from a function in that script. If you need this information from a function, pass it to the function as a parameter.

Create or Remove an Event Log

Problem

You want to create or remove an event log.

Solution

To create an event log, use the [System.Diagnostics.EventLog]:: CreateEventSource() method from the .NET Framework:

$newLog =

NewObject Diagnostics.EventSourceCreationData

"PowerShellCookbook","ScriptEvents"

[Diagnostics.EventLog]::CreateEventSource($newLog)

To delete an event log, use the [System.Diagnostics.EventLog]::Delete() method from the .NET Framework:

[Diagnostics.EventLog]::Delete("ScriptEvents")

Discussion

The [System.Diagnostics.EventLog]::CreateEventSource() method from the .NET Framework registers a new event source (PowerShellCookbook in the solution) to write entries to an event log (ScriptEvents in the solution). If the event log does not exist, the CreateEventSource() method creates the event log.

The [System.Diagnostics.EventLog]::Delete() method from the .NET Framework deletes the event log altogether, along with any event sources associated with it. To delete only a specific event source, use the [System.Diagnostics.EventLog]:: DeleteEventSource() method from the .NET Framework.

Be careful when deleting event logs, as it is difficult to recreate all the event sources if you delete the wrong log by accident.