Nuts and Bolts PowerShell Lead image: © imagesource, 123RF.com
© imagesource, 123RF.com
 

With great PowerShell comes great responsibility

Power to the Shell

Windows administrators, like Unix or Linux administrators, look for ways to perform their duties with ease and elegance through automation, which makes PowerShell a Windows administrator's best friend. By Ken Hess

PowerShell allows administrators to gather information, change Registry parameters, work with Active Directory, manipulate services, look at events, work with Windows Management Instrumentation (WMI), and much more. In this introduction to PowerShell [1], I outline some of the more common problems that administrators face and how to solve them with PowerShell. You won't need programming skills, nor do you need to be adept at scripting of any kind. PowerShell handles the complex tasks behind the scenes by allowing you to work with a series of shortcut commands and parameters (switches) in the foreground.

The strength of PowerShell lies in its simplicity. The cmdlets are easy to learn, easy to use, and easy to extend into an essential toolset for which there is no equivalent. The excitement of PowerShell comes from the ability to manage other systems remotely with cmdlets. PowerShell bestows great power on Windows Administrators – power that, used wisely, will save time, effort, and frustration in environments where hundreds or thousands of systems require attention. PowerShell is in a state of flux; it evolves with each iteration of Microsoft's operating systems. Windows 7 and Windows Server 2008 use PowerShell 2.0, which I use here. Windows 8 and Windows Server 8 will use the new PowerShell 3.0, to be released soon.

To begin, I introduce PowerShell information retrieval to you via the "Get" commands (cmdlets), which allow you to look at system information in a non-destructive way without changing anything. Think of the Get cmdlets as information browsing. Then, I show you how to retrieve information and change it. Finally, I teach you how to control services, processes, and commands on your local system and on remote systems.

Starting the PowerShell Environment

PowerShell cmdlets aren't available in a standard CMD window. To invoke PowerShell, open a CMD window and type powershell. You should see a response similar to this:

C:\Users\khess>powershell
Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.
PS C:\Users\khess>

The PS tells you that you are in PowerShell and that PowerShell is ready to receive instructions in the form of cmdlets, which are shortcut names for programs that do your administrative bidding.

Before going further, you should check your PowerShell version by typing $Host at the PS prompt: (you can also retrieve this information with the Get-Host cmdlet):

PS C:\Users\khess> $Host
Name             : ConsoleHost
Version          : 2.0
...

If the Version parameter doesn't display 2.0, point your browser to the Microsoft support pages [2], scroll down, and download the appropriate components for your system. Once you've downloaded and installed the PowerShell 2.0 components, check your version again to be sure that you have PowerShell 2.0 installed.

PowerShell Syntax

All cmdlets have the following syntax:

PS> Verb-Noun [-Switch <string[]>]

Switches and strings extend the power of the cmdlets but are optional. For example, the cmdlet to list the running processes on your system, Get-Process, is in the Verb-Noun format. The cmdlets are always shown with uppercase and lowercase words, but they're not really case sensitive. Case is used for illustrative purposes only.

Cmdlets have the form, Verb-Noun, and nouns are always singular. Get-Processes, although grammatically satisfying, is syntactically incorrect in PowerShell. When you type Get-Process at the PS prompt, you should see a table listing all of your system's running processes that looks similar to Task Manager's Processes tab.

PowerShell on Your Local System

Before touching production systems with PowerShell cmdlets or scripts, you should practice and test with your local workstation. A virtual machine would be an even better sandbox in which to test. Never test on production systems.

Next, look at the Get-Process cmdlet output. Is it in the format you want? If not, you can change it by passing parameters to switches that the cmdlet interprets then returns your changes to you. The default Get-Process cmdlet reports system process information in the format shown in Listing 1.

Listing 1: Default Get-Process Format

01 Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
02 -------  ------    -----      ----- -----   ------     -- -----------
03      81       9     2392       3076    76     0.12   3996 AmIcoSinglun64
04      74       9     1464       2568    42     0.09   2172 armsvc
05     195      10     2380       2984    62     0.50   1164 atieclxx
06     102       9     2024       2380    34     0.08    960 atiesrxx
07     118       9    15648      15772    50            2664 audiodg
08    1337     528    42548      61444   284 2,611.69   1540
09 ...

Processes display in descending alphabetical order by ProcessName, but this is not that useful to an administrator who needs to know which process is consuming too much CPU time, for which you need to sort by CPU consumption. If you use Linux, the top command is the equivalent of what you want to see here. To sort by CPU time, issue the cmdlet:

PS> Get-Process | Sort-Object -Descending -Property CPU

This example shows PowerShell syntax and piping from one cmdlet to another. To use this more advanced cmdlet syntax, you have to begin by writing out the desired results from your script: For example, "list processes (easy) in order of highest CPU usage to lowest."

With PowerShell, you can pipe the output of one cmdlet into the input of another cmdlet. If you're also a Unix or Linux shell scripter, then you know the power of the pipe. If not, you'll discover it with PowerShell.

However, before you can effectively pipe the output of one cmdlet into another, you need to know what the output is and how you want it configured when piped to the other cmdlet. You also need to know what your options are for filtering output from both cmdlets. An extensive Help system is available to assist you, which is outlined in more detail in the "PowerShell Help System" section later in this article.

You aren't limited to processes on your system; you can also look at the status of services with a command such as:

PS> Get-Service

In Listing 2, you can see that the Get-Service cmdlet returns a table displaying the Status, Name, and DisplayName of all the services on your system. Although this is useful information, if you want to look at a single service or a group of related services, a display of all services is a bit cumbersome.

Listing 2: Get-Service

01 PS> Get-Service
02 Status   Name               DisplayName
03 ------   ----               -----------
04 Running  AdobeARMservice    Adobe Acrobat Update Service
05 Stopped  AeLookupSvc        Application Experience
06 Stopped  ALG                Application Layer Gateway Service
07 Running  AMD External Ev... AMD External Events Utility
08 Stopped  AppIDSvc           Application Identity
09 Stopped  Appinfo            Application Information

Notice in Listing 3 that I use the -Name switch to filter on the service name. If you try -DisplayName in your filter, when you enter -Status, an error is returned from the system because Status is not a parameter you can manipulate directly. It's a bit advanced at this point to explain why, so I'll tell you that, when you find such a property, you'll have to use a workaround.

Listing 3: Filtering with Get-Service

01 PS> Get-Service -Name wmi*
02 Status   Name               DisplayName
03 ------   ----               -----------
04 Stopped  wmiApSrv           WMI Performance Adapter

One workaround is to use Sort-Object. For example, to see a list of all services and their statuses, filtered by status, enter:

PS> Get-Service | Sort-Object Status

Listing 4 shows the result.

Listing 4: Filtering by Status

01 PS> Get-Service | Sort-Object Status
02 Status   Name               DisplayName
03 ------   ----               -----------
04 Stopped  QWAVE              Quality Windows Audio Video Experience
05 Stopped  QBFCService        Intuit QuickBooks FCS
06 Stopped  RasMan             Remote Access Connection Manager
07 Stopped  RasAuto            Remote Access Auto Connection Manager
08 Stopped  WwanSvc            WWAN AutoConfig

PowerShell on Remote Systems

Using PowerShell on a local system is interesting and useful, but the real power of PowerShell is using it with remote systems. The beauty of PowerShell is that you don't have to RDP or connect remotely in any direct way to run PowerShell commands on a remote system to which you have access: You just have to specify the remote computer's name with the -ComputerName switch and the name of the remote system. If you do not specify a computer name, the command defaults to the local system.

For example, to see a list of running processes on a remote system, you can enter:

PS> Get-Process -ComputerName <Remote System Name>

My remote Windows server name is XenApp1, so I'd use that with the -ComputerName switch. To view services or a subset of running services, you can run a similar cmdlet. In Listing 5, I wanted to see all services related to the 2X service.

Listing 5: View a Subset of Services

01 PS> Get-Service -ComputerName XenApp1 -Name 2X*
02 Status   Name               DisplayName
03 ------   ----               -----------
04 Running  2X Publishing A... 2X Publishing Agent
05 Running  2X Redundancy S... 2X Redundancy Service
06 Running  2X SecureClient... 2X SecureClientGateway
07 Running  2X Terminal Ser... 2X Terminal Server Agent

By now, you should be able to see the pattern of how PowerShell works for local versus remote systems. Basically, any cmdlet that works locally will also work remotely if you specify -ComputerName <Remote Computer Name> in the syntax.

The PowerShell Help System

If you're going to use PowerShell with any of its advanced features, or if you're going to write your own scripts, you'll need to learn to use PowerShell's Help system.

To get help, you simply type the cmdlet Get-Help at the PS prompt. To find help with a specific cmdlet, use the Get-Help cmdlet followed by the command with which you need assistance:

PS> Get-Help Get-Process

The Help system looks like Unix-style man pages, with the cmdlet name (NAME), a short description (SYNOPSIS), the cmdlet syntax (SYNTAX), an extended description (DESCRIPTION), links and related commands (RELATED LINKS), and additional information (REMARKS).

To see examples of the cmdlet you're interested in, use the Get-Help command like this:

PS> Get-Help Get-Process -examples

PowerShell also understands wildcards (* ?). For example, to see a list of all of the Get cmdlets, use:

PS> Get-Help Get*

To see a list of all possible Get cmdlets with a four-letter Noun, use:

PS> Get-Help Get?????

The five ?s represent the hyphen (-) and the four Noun positions.

The cmdlets under RELATED LINKS could also be useful. For example, Get-Process has four related cmdlets:

You now have enough information to practice running Get cmdlets on your local system and on remote systems to view system information. If you venture further into this powerful language and its possibilities in your environment, please set up a virtual machine so you don't irreversibly damage a live or production system.

Starting and Stopping Services

One of the basic duties of an Administrator is to start, stop, and restart services on systems. PowerShell empowers you to do so on a local system and on remote systems. Manipulating services on a local system takes PowerShell scripting to an overly complex extreme, but it demonstrates the syntax and the necessary switches (parameters) required to control services on remote systems.

Although you can't directly manipulate services on remote systems, it is possible to do so programmatically. It adds an additional level of complexity to your task, but the result is worth the trouble. How do you know if a cmdlet has remote system capability? Use the help system

C:\> Get-Help Get-Service

and focus on the SYNTAX section of the help listing (Listing 6).

Listing 6: Get-Help SYNTAX Section

SYNTAX
    Get-Service [[-Name] <string[]>] [-ComputerName <string[]>]
      [-DependentServices] [-Exclude <string[]>] [-Include <string[]>]
      [-RequiredServices] [<CommonParameters>]
    Get-Service -DisplayName <string[]> [-ComputerName <string[]>]
      [-DependentServices] [-Exclude <string[]>] [-Include <string[]>]
      [-RequiredServices] [<CommonParameters>]
    Get-Service [-InputObject <ServiceController[]>] [-ComputerName <string[]>]
      [-DependentServices] [-Exclude <string[]>] [-Include <string[]>]
      [-RequiredServices] [<CommonParameters>]

Note that one of the optional Get-Service parameters is -ComputerName. This parameter means the Get-Service cmdlet has the ability to extract information from remote systems named by the -ComputerName switch.

However, if you were to look at the Start-Service or Stop-Service service manipulation cmdlets, you would not see -ComputerName or anything related to remote computers.

Listing 7 is an example from my local system. I need to check the status and start the Windows Defender service if it isn't already started. The output tells me that the service is not only in a Stopped state but is Disabled.

Listing 7: Get-Service on a Local System

01 PS C:\Users\khess> Get-Service -DisplayName 'Windows Defender'
02 Status   Name               DisplayName
03 ------   ----               -----------
04 Stopped  WinDefend          Windows Defender
05 PS C:\Users\khess> Start-Service -DisplayName 'Windows Defender'
06 Start-Service : Service 'Windows Defender (WinDefend)' cannot be started due
07 to the following error: Cannot start service WinDefend on computer '.'.
08 At line:1 char:14
09 + start-service <<<<  -DisplayName 'Windows Defender'
10     + CategoryInfo          : OpenError:
11 (System.ServiceProcess.ServiceController:ServiceController) [Start-Service],
12 ServiceCommandException + FullyQualifiedErrorId :
13 CouldNotStartService,Microsoft.PowerShell.Commands.StartServiceCommand

A quick visual Services check, as shown in Figure 1, shows that the Windows Defender Service is in a Disabled state. You can't start a disabled service; you have to change its startup type to Automatic or Manual before you can start the service.

Windows 7 Services highlighting the disabled Windows Defender service.
Figure 1: Windows 7 Services highlighting the disabled Windows Defender service.

In PowerShell, however, you can perform a status change with the Set-Service cmdlet:

PS C:\Users\khess> Set-Service -DisplayName 'Windows Defender' -StartupType Automatic
cmdlet Set-Service at command pipeline position 1
Supply values for the following parameters:
Name: WinDefend

Now, you can start the service by issuing the Start-Service cmdlet:

PS C:\Users\khess> Start-Service -DisplayName 'Windows Defender'

It's always wise to check the status of a service that you change for confirmation of its current condition (Listing 8).

Listing 8: Checking Service Status

01 PS C:\Users\khess> Get-Service -DisplayName 'Windows Defender'
02 Status   Name               DisplayName
03 ------   ----               -----------
04 Running  WinDefend          Windows Defender

This is a nice exercise, but it's easier to start and stop Windows services on a local system via the Computer Management Services application. Starting, stopping, and changing the service startup type on remote systems in an automated fashion is not so straightforward. As I stated earlier, to perform this function, you have to do so programmatically.

The Windows Telnet Service is disabled by default on all server systems because it is a non-secure protocol. That is to say, the Telnet client and server exchange usernames and passwords in cleartext, which can be easily captured and used to compromise systems. However, it has all of the elements of a Windows service that's perfect for demonstration purposes.

To list the services and their statuses remotely, I try:

PS C:\> Get-Service -ComputerName XENAPP0
Get-Service : Cannot open Service Control Manager on computer 'XENAPP0'. This operation might require other privileges.
...

The output tells me that the remote system doesn't allow remote management. To enable remote management, I need to connect to the system on which I'm trying to run the remote command, launch a CMD window as Administrator, and run:

C:\Users\Administrator>winrm quickconfig

After answering Yes to the questions that follow, I will be able to run remote commands on my systems.

Now when I rerun Get-Service on the remote system, I see useful output. In Listing 9, you can see that the Telnet Server service is in a Stopped state and most likely is also Disabled. Your first inclination might be to issue a command such as

Listing 9: Get-Service on a Remote System

01 PS C:\> Get-Service -ComputerName XENAPP0
02 Status   Name               DisplayName
03 ------   ----               -----------
04 ...
05 Running  TermService        Remote Desktop Services
06 Stopped  THREADORDER        Thread Ordering Server
07 Stopped  TlntSvr            Telnet
08 Running  TrkWks             Distributed Link Tracking Client
09 ...
PS C:\> Start-Service -ComputerName XENAPP0 -DisplayName Telnet

to start the service. Doing so would return the expected error:

Start-Service : A parameter cannot be found that matches parameter name 'ComputerName'.
...

Fortunately, Microsoft provides a -ComputerName switch for the Set-Service cmdlet that makes the task of remotely setting its StartupType parameter to Automatic or Manual direct and easy:

PS C:\> Set-Service -ComputerName XENAPP0 -DisplayName Telnet -StartupType Manual
cmdlet Set-Service at command pipeline position 1
Supply values for the following parameters:
Name: TlntSvr

Note that you're prompted to enter the name of the service (TlntSvr) to complete the action. Although the status of the Telnet Server service will not have changed if you enter Get-Service again, its StartupType is set to Manual. The service is now in a "startable" state. There's no -ComputerName parameter available for the Start-Service cmdlet. After a lengthy search, however, I found the following method to start a service on a remote system:

PS C:\> (Get-WmiObject -Computer XENAPP0 Win32_Service -Filter "Name='TlntSvr'").InvokeMethod("StartService",$null)

I checked out several methods that claimed to be successful, but this is the only one that did it for me. After issuing this command, I received a single 0 as a return value, which indicates success. Checking the status of the Telnet Server service again outputs the following line of interest:

Status   Name               DisplayName
------   ----               -----------
...
Running  TlntSvr            Telnet
...

To stop the service, just replace Start-Service with Stop-Service in the script. This technique works for any Windows service.

Starting and Stopping Processes

The description of the Start-Process cmdlet is: "Starts one or more processes on the local computer." However, you probably realize by now that there will be a way to get around this egregious limitation. That workaround is known as the Invoke-Command cmdlet. Invoke-Command, as stated in the cmdlet SYNOPSIS, runs commands on local and remote computers. For example, if there's a hung process on a remote system running in a CMD window, you can find it and stop that process with a simple Invoke-Command (Listing 10).

Listing 10: Invoke-Command

01 PS C:\> Get-Process -ComputerName XENAPP0
02 PS C:\Users\khess> Get-Process -ComputerName XENAPP0
03 Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
04 -------  ------    -----      ----- -----   ------     -- -----------
05      80       9     1292       4548    33             336 CdfSvc
06      23       5     1996       3084    47            2208 cmd
07      36       5     1180       4296    60            1724 conhost
08      37       6     1908       4848    61            2644 conhost
09 ...

Note the Id of the cmd process (2208). When you stop a process, you must know its Id and send a signal to the remote system to kill or stop that process Id:

PS C:\Users\khess> Invoke-Command -ComputerName XENAPP0 -ScriptBlock {Stop-Process -Id 2208}
Confirm
Are you sure you want to perform the Stop-Process operation on the following item: cmd(2208)?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All [?] Help (default is "Y"): Y

The process will end immediately on receipt of the Stop-Process signal.

To run a CMD process by substituting Start-Process and cmd for a process name. The command would look like this:

PS C:\Users\khess> Invoke-Command -ComputerName XENAPP0 -ScriptBlock {Start-Process cmd -PassThru }

The -PassThru switch allows you to send output to your screen. Without that switch, you'll see no output onscreen. You might assume that the script successfully created a CMD window on the remote system, but you'll find that this did not happen. Well, it did happen momentarily, but the remote CMD window launches and then dies.

Windows, by design, doesn't allow you to launch programs in this way. That said, it is still possible to do so programmatically. However, doing so is far outside the scope of an introductory PowerShell article.

Running Commands on Remote Computers

Via PowerShell, you have the ability to run commands on remote computers that have non-interactive output to the screen. For example, if you want to see a quick netstat on a system, you can do so by issuing the command:

PS C:\Users\khess> Invoke-Command { netstat } -ComputerName XENAPP0

Or, do the following to check the TCP/IP configuration:

PS C:\Users\khess> Invoke-Command { ipconfig /all } -ComputerName XENAPP0

If you try launching a CMD window this way, you'll understand what happens when you attempt to run an interactive application (Listing 11). The CMD does launch, but dies, on the remote system, and you receive screen output from the results of that launch.

Listing 11: Attempt to Run Interactive App

01 PS C:\> Invoke-Command { cmd } -ComputerName XENAPP0
02 Microsoft Windows [Version 6.1.7600]
03 Copyright (c) 2009 Microsoft Corporation.  All rights reserved.
04 C:\Users\khess\Documents>   <--Remote system directory.
05 C:\>   <--Local system prompt.

If you're familiar with the PsTools suite, now owned by Microsoft, the PsExec command performs a similar function, as do these commands in PowerShell. To do so, you launch a command on a remote system in a non-interactive way, send the command, and receive a response. Although you can carry on an interactive PowerShell session, that's the topic of a future article.

Summary

PowerShell is an important tool to add to your Windows administrator toolbox. The more experience you gain with this essential tool, the better you'll like it. PowerShell isn't a traditional scripting language, although it does include many of the same attributes: variables, prompted input, redirected output, looping, and advanced decision making. If you're new to PowerShell, practice with the Get cmdlets for a while before attempting any Set or other intrusive cmdlets.

I hope that you can see the potential for PowerShell in your environment. Be aware that systems in a domain act differently from those in a standalone environment. Administrators might have to make domain-wide policy changes to allow remote management on systems. PowerShell and remote management are system administration tools and aren't necessarily inherent security risks, but you might have difficulty when pleading your case to your security team.

Typical users (those without Local Administrator or Domain Administrator privileges) can't run these commands. PowerShell has security checks built in so that non-administrator staff can't issue system-changing commands and wreak havoc in your environment.