Skip to main content

Windows

Convert a String to Upper/Lowercase in Windows PowerShell

Problem

You want to convert a string to uppercase or lowercase.

Solution

Use the ToUpper() and ToLower() methods of the string to convert it to uppercase and lowercase, respectively. To convert a string to uppercase, use the ToUpper() method:

PS >"Hello World".ToUpper()

HELLO WORLD To convert a string to lowercase, use the ToLower() method:

PS >"Hello World".ToLower()

hello world

Discussion

Since PowerShell strings are fully featured .NET objects, they support many stringoriented operations directly. The ToUpper() and ToLower() methods are two examples of the many features that the String class supports.

Neither PowerShell nor the methods of the .NET String class directly support capitalizing only the first letter of a word. If you want to capitalize only the first character of a word or sentence, try the following

commands:

PS >$text = "hello" PS >$newText = $text.Substring(0,1).ToUpper() + >> $text.Substring(1) >> $newText >> Hello

One thing to keep in mind as you convert a string to uppercase or lowercase is your motivation for doing it. One of the most common reasons is for comparing strings, as shown in Example 54.

Example 54. Using the ToUpper() method to normalize strings

## $text comes from the user, and contains the value "quit" if($text.ToUpper() eq "QUIT") { ... }

Unfortunately, explicitly changing the capitalization of strings fails in subtle ways when your script runs in different cultures. Many cultures follow different capitalization and comparison rules than you may be used to. For example, the Turkish language includes two types of the letter “I”: one with a dot, and one without. The uppercase version of the lowercase letter “i” corresponds to the version of the capital I with a dot, not the capital I used in QUIT. Those capitalization rules cause the string comparison code in Example 54 to fail in the Turkish culture.

To compare some input against a hardcoded string in a caseinsensitive manner, the better solution is to use PowerShell’s –eq operator without changing any of the casing yourself. The –eq operator is caseinsensitive and cultureneutral by default:

PS >$text1 = "Hello" PS >$text2 = "HELLO" PS >$text1 –eq $text2 True

Use .NET to Perform Advanced WMI Tasks

Problem

You want to work with advanced features of WMI, but PowerShell’s access (through the [Wmi], [WmiClass], and [WmiSearcher] accelerators) does not directly support them.

Solution

To interact with advanced features through their .NET interface, use the PsBase property of the resulting objects.

Advanced instance features

To get WMI instances related to a given instance, call the GetRelated() method:

$instance = [Wmi] 'Win32_Service.Name="winmgmt"' $instance.PsBase.GetRelated()

To enable security privileges for a command that requires them (such as changing the system time), set the EnablePrivileges property to $true:

$system = GetWmiObject Win32_OperatingSystem $system.PsBase.Scope.Options.EnablePrivileges = $true $system.SetDateTime($class.ConvertFromDateTime("01/01/2007"))

Advanced class features

To retrieve the WMI properties and qualifiers of a class, access the PsBase. Properties property: $class = [WmiClass] "Win32_Service" $class.PsBase.Properties

Advanced query feature

To configure connection options, such as Packet Privacy and Authentication, set the options on the Scope property:

$credential = GetCredential $query = [WmiSearcher] "SELECT * FROM IISWebServerSetting" $query.Scope.Path = "\\REMOTE_COMPUTER\Root\MicrosoftIISV2" $query.Scope.Options.Username = $credential.Username $query.Scope.Options.Password = $credential.GetNetworkCredential().Password $query.Scope.Options.Authentication = "PacketPrivacy" $query.get() | SelectObject AnonymousUserName

Discussion

The [Wmi], [WmiClass], and [WmiSearcher] type shortcuts return instances of .NET System.Management.ManagementObject, System.Management.ManagementClass, and System.Management.ManagementObjectSearcher classes, respectively.

As might be expected, the .NET Framework provides comprehensive support for WMI queries, with PowerShell providing an easiertouse interface to that support. If you need to step outside the support offered directly by PowerShell, these classes in the .NET Framework provide an advanced outlet.

Access Services on a Windows PowerShell Remote Machine

Problem

You want to list or manage services on a remote machine.

Solution

To retrieve the services from a remote machine, use the [System.ServiceProcess. ServiceController]::GetServices() method from the .NET Framework.

PS >[void] ([Reflection.Assembly]::LoadWithPartialName("System.ServiceProcess"))

PS >[System.ServiceProcess.ServiceController]::GetServices("LEEDESK")

Status
Name
DisplayName

Running
ADAM_Test
Test

Stopped
Alerter
Alerter

Running
ALG
Application Layer Gateway Service

Stopped
AppMgmt
Application Management

Stopped
aspnet_state
ASP.NET State Service

Running
AudioSrv
Windows Audio

Running
BITS
Background Intelligent Transfer Ser...

Running
Browser
Computer Browser

Stopped
CiSvc
Indexing Service

To control one, use the WhereObject cmdlet to retrieve that one specifically and then call the methods on the object that manage it:

[void] ([Reflection.Assembly]::LoadWithPartialName("System.ServiceProcess"))

$service = [System.ServiceProcess.ServiceController]::GetServices("LEEDESK") | WhereObject { $_.Name eq "Themes" }

$service.Stop() $service.WaitForStatus("Stopped") StartSleep 2 $service.Start()

Discussion

If you have administrator privileges on a remote machine, the [System. ServiceProcess.ServiceController]::GetServices() method from the .NET Framework lets you control services on that machine.

When doing this, note that both of the examples from the solution require that you first load the assembly that contains the .NET classes that manage services. The *Service cmdlets load this DLL automati

cally.

Reduce Typing for Long Class Names

Problem

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

Solution

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

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

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

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

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

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

Discussion

One thing you will notice when working with some .NET classes (or classes from a thirdparty 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 = NewObject ($namespace f "ArrayList") $queue = NewObject ($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()

Program: Analyze a Script’s Performance Profile

When you write scripts that heavily interact with the user, you may sometimes feel that your script could benefit from better performance.

When tackling performance problems, the first rule is to measure the problem. Unless you can guide your optimization efforts with hard performance data, you are almost certainly directing your efforts to the wrong spots. Random cute performance improvements will quickly turn your code into an unreadable mess, often with no appreciable performance gain! Lowlevel optimization has its place, but it should always be guided by hard data that supports it.

The way to obtain hard performance data is from a profiler. PowerShell doesn’t ship with a script profiler, but Example 135 uses PowerShell features to implement one.

Example 135. GetScriptPerformanceProfile.ps1

################################################################################ ## ## GetScriptPerformanceProfile.ps1 ## ## Computes the performance characteristics of a script, based on the transcript ## of it running at trace level 1. ## ## To profile a script:

##
1) Turn on script tracing in the window that will run the script:

##
SetPsDebug trace 1

##
2) Turn on the transcript for the window that will run the script:

##
StartTranscript

##
(Note the filename that PowerShell provides as the logging destination.)

##
3) Type in the script name, but don't actually start it.

##
4) Open another PowerShell window, and navigate to the directory holding

##
this script.
Type in 'GetScriptPerformanceProfile
',

##
replacing
with the path given in step 2.
Don't

##
press yet.

##
5) Switch to the profiled script window, and start the script.

##
Switch to the window containing this script, and press

##
6) Wait until your profiled script exits, or has run long enough to be

##
representative of its work.
To be statistically accurate, your script

##
should run for at least ten seconds.

##
7) Switch to the window running this script, and press a key.

##
8) Switch to the window holding your profiled script, and type:

##
StopTranscript

##
9) Delete the transcript.

##

## Note: You can profile regions of code (ie: functions) rather than just lines ## by placing the following call at the start of the region: ## writedebug "ENTER " ## and the following call and the end of the region: ## writedebug "EXIT" ## This is implemented to account exclusively for the time spent in that ## region, and does not include time spent in regions contained within the ## region. For example, if FunctionA calls FunctionB, and you've surrounded ## each by region markers, the statistics for FunctionA will not include the ## statistics for FunctionB. ## ################################################################################

Example 135. GetScriptPerformanceProfile.ps1 (continued)

param($logFilePath = $(throw "Please specify a path to the transcript log file."))

function Main

{ ## Run the actual profiling of the script. $uniqueLines gets ## the mapping of line number to actual script content. ## $samples gets a hashtable mapping line number to the number of times ## we observed the script running that line. $uniqueLines = @{} $samples = GetSamples $uniqueLines

"Breakdown by line:" ""

## Create a new hash table that flips the $samples hashtable ## one that maps the number of times sampled to the line sampled. ## Also, figure out how many samples we got altogether. $counts = @{} $totalSamples = 0; foreach($item in $samples.Keys) {

$counts[$samples[$item]] = $item $totalSamples += $samples[$item] }

## Go through the flipped hashtable, in descending order of number of ## samples. As we do so, output the number of samples as a percentage of ## the total samples. This gives us the percentage of the time our script ## spent executing that line. foreach($count in ($counts.Keys | SortObject Descending)) {

$line = $counts[$count] $percentage = "{0:#0}" f ($count * 100 / $totalSamples) "{0,3}%: Line {1,4} {2}" f $percentage,$line,

$uniqueLines[$line] }

## Go through the transcript log to figure out which lines are part of any ## marked regions. This returns a hashtable that maps region names to ## the lines they contain. "" "Breakdown by marked regions:" "" $functionMembers = GenerateFunctionMembers

## For each region name, cycle through the lines in the region. As we ## cycle through the lines, sum up the time spent on those lines and output ## the total. foreach($key in $functionMembers.Keys) {

$totalTime = 0 foreach($line in $functionMembers[$key])

Example 135. GetScriptPerformanceProfile.ps1 (continued)

{ $totalTime += ($samples[$line] * 100 / $totalSamples) }

$percentage = "{0:#0}" f $totalTime "{0,3}%: {1}" f $percentage,$key } }

## Run the actual profiling of the script. $uniqueLines gets ## the mapping of line number to actual script content. ## Return a hashtable mapping line number to the number of times ## we observed the script running that line. function GetSamples($uniqueLines) {

## Open the log file. We use the .Net file I/O, so that we keep monitoring ## just the end of the file. Otherwise, we would make our timing inaccurate ## as we scan the entire length of the file every time. $logStream = [System.IO.File]::Open($logFilePath, "Open", "Read", "ReadWrite") $logReader = NewObject System.IO.StreamReader $logStream

$random = NewObject Random $samples = @{}

$lastCounted = $null

## Gather statistics until the user presses a key. while(not $host.UI.RawUI.KeyAvailable) {

## We sleep a slightly random amount of time. If we sleep a constant ## amount of time, we run the very real risk of improperly sampling ## scripts that exhibit periodic behaviour. $sleepTime = [int] ($random.NextDouble() * 100.0) StartSleep Milliseconds $sleepTime

## Get any content produced by the transcript since our last poll. ## From that poll, extract the last DEBUG statement (which is the last ## line executed.) $rest = $logReader.ReadToEnd() $lastEntryIndex = $rest.LastIndexOf("DEBUG: ")

## If we didn't get a new line, then the script is still working on the ## last line that we captured. if($lastEntryIndex lt 0) {

if($lastCounted) { $samples[$lastCounted] ++ } continue; }

## Extract the debug line. $lastEntryFinish = $rest.IndexOf("\n", $lastEntryIndex) if($lastEntryFinish eq 1) { $lastEntryFinish = $rest.length }

Example 135. GetScriptPerformanceProfile.ps1 (continued)

$scriptLine = $rest.Substring(

$lastEntryIndex, ($lastEntryFinish $lastEntryIndex)).Trim() if($scriptLine match 'DEBUG:[ \t]*([09]*)\+(.*)') {

## Pull out the line number from the line $last = $matches[1]

$lastCounted = $last $samples[$last] ++

## Pull out the actual script line that matches the line number $uniqueLines[$last] = $matches[2] }

## Discard anything that's buffered during this poll, and start waiting ## again $logReader.DiscardBufferedData()

}

## Clean up $logStream.Close() $logReader.Close()

$samples }

## Go through the transcript log to figure out which lines are part of any ## marked regions. This returns a hashtable that maps region names to ## the lines they contain. function GenerateFunctionMembers {

## Create a stack that represents the callstack. That way, if a marked ## region contains another marked region, we attribute the statistics ## appropriately. $callstack = NewObject System.Collections.Stack $currentFunction = "Unmarked" $callstack.Push($currentFunction)

$functionMembers = @{}

## Go through each line in the transcript file, from the beginning foreach($line in (GetContent $logFilePath)) {

## Check if we're entering a monitor block ## If so, store that we're in that function, and push it onto ## the callstack. if($line match 'writedebug "ENTER (.*)"') {

$currentFunction = $matches[1] $callstack.Push($currentFunction) }

Example 135. GetScriptPerformanceProfile.ps1 (continued)

## Check if we're exiting a monitor block ## If so, clear the "current function" from the callstack, ## and store the new "current function" onto the callstack. elseif($line match 'writedebug "EXIT"') {

[void] $callstack.Pop()

$currentFunction = $callstack.Peek() } ## Otherwise, this is just a line with some code. ## Add the line number as a member of the "current function" else {

if($line match 'DEBUG:[ \t]*([09]*)\+')

{ ## Create the arraylist if it's not initialized if(not $functionMembers[$currentFunction]) {

$functionMembers[$currentFunction] = NewObject System.Collections.ArrayList }

## Add the current line to the ArrayList if(not $functionMembers[$currentFunction].Contains($matches[1])) {

[void] $functionMembers[$currentFunction].Add($matches[1]) } } } }

$functionMembers }

. Main

Get the Newest Entries from an Event Log

Problem

You want to retrieve the most recent entries from an event log.

Solution

To retrieve the most recent entries from an event log, use the –Newest parameter of the GetEventLog cmdlet, as shown in Example 201.

Example 201. Retrieving the 10 newest entries from the System event log

PS >GetEventLog System Newest 10 | FormatTable Index,Source,Message A

Index Source Message

2922 Service Control Manager The Background Intelligent Transfer Servi... 2921 Service Control Manager The Background Intelligent Transfer Servi... 2920 Service Control Manager The Logical Disk Manager Administrative S... 2919 Service Control Manager The Logical Disk Manager Administrative S... 2918 Service Control Manager The Logical Disk Manager Administrative S...

2917 TermServDevices
Driver Microsoft XPS Document Writer requ...

2916 Print
Printer Microsoft Office Document Image W...

2915 Print
Printer Microsoft Office Document Image W...

2914 Print
Printer Microsoft Office Document Image W...

2913 TermServDevices
Driver Microsoft Shared Fax Driver requir...

Discussion

The –Newest parameter of the GetEventLog cmdlet retrieves the most recent entries from an event log that you specify.

For more information about the GetEventLog cmdlet, type GetHelp GetEventLog.

Windows PowerShell Configure Debug, Verbose, and Progress Output

Problem

You want to manage the detailed debug, verbose, and progress output generated by cmdlets and scripts.

Solution

To enable debug output for scripts and cmdlets that generate it:

$debugPreference = "Continue" StartDebugCommand

To enable verbose mode for a cmdlet that checks for the Verbose parameter:

CopyItem c:\temp\*.txt c:\temp\backup\ Verbose

To disable progress output from a script or cmdlet that generates it:

$progressPreference = "SilentlyContinue" GetProgress.ps1

Discussion

In addition to error output many scripts and cmdlets generate several other types of output. This includes:

Debug output

Helps you diagnose problems that may arise and can provide a view into the inner workings of a command. You can use the WriteDebug cmdlet to produce this type of output in a script or the WriteDebug( ) method to produce this type of output in a cmdlet. PowerShell displays this output in yellow, unless you customize it through the $host.PrivateData.Debug* color configuration variables.

Verbose output

Helps you monitor the actions of commands at a finer level than the default. You can use the WriteVerbose cmdlet to produce this type of output in a script or the WriteVerbose( ) method to produce this type of output in a cmdlet. PowerShell displays this output in yellow, unless you customize it through the $host. PrivateData.Verbose* color configuration variables.

Progress output

Helps you monitor the status of longrunning commands. You can use the WriteProgress cmdlet to produce this type of output in a script or the WriteProgress( ) method to produce this type of output in a cmdlet. PowerShell displays this output in yellow, unless you customize it through the $host. PrivateData.Progress* color configuration variables.

Some cmdlets generate verbose and debug output only if you specify the Verbose and Debug parameters, respectively.

To configure the debug, verbose, and progress output of a script or cmdlet, modify the $debugPreference, $verbosePreference, and $progressPreference shell variables. These variables can accept the following values:

SilentlyContinue

Do not display this output.

Stop

Treat this output as an error.

Continue

Display this output.

Inquire

Display a continuation prompt for this output.

Windows PowerShell User Interaction

While most scripts are designed to run automatically, you will frequently find it useful to have your scripts interact with the user.

The best way to get input from your user is through the arguments and parameters to your script or function. This lets your users to run your script without having to be there as it runs!

If your script greatly benefits from (or requires) an interactive experience, PowerShell offers a range of possibilities. This might be simply waiting for a keypress, prompting for input, or displaying a richer choicebased prompt.

User input isn’t the only aspect of interaction though. In addition to its input facilities, PowerShell supports output as well—from displaying simple text strings to much more detailed progress reporting and interaction with UI frameworks.

Add a Site to an Internet Explorer Security Zone

Problem

You want to add a site to a specific Internet Explorer security zone.

Solution

To create the registry keys and properties required to add a site to a specific security zone, use the NewItem and NewItemProperty cmdlets. Example 183 adds www. example.com to the list of sites trusted by Internet Explorer.

Example 183. Adding www.example.com to the list of trusted sites in Internet Explorer

SetLocation "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" SetLocation ZoneMap\Domains NewItem example.com SetLocation example.com NewItem www SetLocation www NewItemProperty . Name http Value 2 Type DWORD

The Internet Explorer zone identifiers are:

  1. My Computer
  2. Local intranet
  3. Trusted sites
  4. Internet
  5. Restricted sites

When Internet Explorer is configured in its Enhanced Security Configuration mode, you must also update entries under the EscDomains key.

Once a machine has enabled Internet Explorer’s Enhanced Security Configuration, those settings persist even after removing Enhanced Security Configuration. The following commands allow your machine

to trust UNC paths again:

SetLocation "HKCU:\Software\Microsoft\Windows\" SetLocation "CurrentVersion" SetLocation "Internet Settings" SetItemProperty ZoneMap UNCAsIntranet Type DWORD 1 SetItemProperty ZoneMap IntranetName Type DWORD 1

To remove the zone mapping for a specific domain, use the RemoveItem cmdlet: PS >GetChildItem

Hive: Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Mi crosoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains

SKC
VC Name
Property

1

0 example.com
{}

PS >RemoveItem Recurse example.com PS >GetChildItem PS >

For more information about using the Internet Explorer registry entries to configure security zones, see the Microsoft KB article “Description of Internet Explorer Security Zones Registry Entries” at http://support.microsoft.com/kb/182569. For more information about managing Internet Explorer’s Enhanced Security Configuration, search for it on http://technet.microsoft.com.

Manage Outlook Web Access

Problem

You want to get and modify Outlook Web Access settings from the Exchange Management Shell.

Solution

To retrieve information about an Outlook Web Access virtual directory, use the GetOwaVirtualDirectory cmdlet:

$owa = GetOwaVirtualDirectory "owa (Default Web Site)"

To modify information about that virtual directory, use the SetOwaVirtualDirectory cmdlet. This example prevents users from changing their passwords through Outlook Web Access:

$owa | SetOwaVirtualDirectory –ChangePasswordEnabled:$false

Discussion

For more information about the GetOwaVirtualDirectory cmdlet, type GetHelp GetOwaVirtualDirectory. For more information about the SetOwaVirtualDirectory cmdlet, type GetHelp SetOwaVirtualDirectory.

Manage an Operations Manager 2007 Server

Like Exchange 2007, System Center Operations Manager 2007 (previously known as Microsoft Operations Manager) broadly adopts PowerShell as a technique for automated management. Operations Manager 2007 takes a dualpronged approach to this. It includes a set of more than 70 PowerShell cmdlets, as well as a PowerShell provider that lets you scope commands to (and navigate) management groups.

The most common management tasks in Operations Manager 2007 fall largely into one of several categories: managing agents, management packs, rules, tasks, alerts, and maintenance windows.