In our first example we'll create a script that restarts a list of computers. The list will come from a text file that contains one computer name per line. The file name will be specified as a parameter of the script. We're assuming the person running the script is a local administrator on each listed computer. With those basic requirements stated, you'll find the complete script in the listing Restart-Computers.ps1. You can run this script by double-clicking it in Windows.
Security Precautions
You may have trouble getting this script to run if PowerShell is still in its default configuration on your computer. Don't worry about this for now since we'll dive into security restrictions and the reason this script might not run in the next chapter. By default PowerShell is quite "locked-down", to the point that when you first install it, it won't run any scripts at all.
Restart-Computers.ps1
# Define input parameters param ( [string] $filename = $(throw "Filename is required!") ) Write-Host "Reading computer names from $filename" # Read file $computers = get-content $filename foreach ($computer in $computers) { # Connect to WMI $wmi = get-wmiobject -class "Win32_OperatingSystem" ` -namespace "root\cimv2" -computer $computer # Restart computer foreach ($item in $wmi) { $wmi.reboot() write-host "Restarted " + $computer } }
So how does this work? Let's look at each line. First, the script defines its input parameters. This is done in a Param block that simply defines the input parameters for a script. Notice that we've specified a specific data type, [string], for the input parameter. Also notice that we've given the input parameter a special default value. Normally, the default value is assigned to the variable if the parameter isn't specified. However, in this case, our "default" is to throw an exception, which means the rest of the script won't run unless the $filename parameter is specified. The Throw keyword simply means "produce an error", and it's followed by the error message we wanted to produce.
If our parameter was specified, we'd just write out the filename using Write-Host. The Write-Host cmdlet outputs whatever you tell it such as descriptive messages. Notice that the $filename variable will be evaluated by PowerShell, and its contents will be displayed because the string it is contained within is delimited by double quotation marks.
# Define input parameters param ( [string] $filename = $(throw "Filename is required!") ) Write-Host "Reading computer names from $filename"
Next, we will read the file using the Get-Content cmdlet, which needs the filename. We'll save the results into the $computers variable that will basically be a collection (or array) of string objects, with each string object being one computer name. Recall that the file is formatted with one computer name per line.
Note | A complete cmdlet reference is provided in Chapter 14. |
We'll use a foreach loop to go through the collection of names. Each time through this loop, the variable $computer will represent the current item or computer name. The foreach loop will be covered in greater detail in Chapter 8.
# Read file $computers = get-content $filename foreach ($computer in $computers) {
Now it's time to connect to WMI. Since this code is contained within the foreach loop, it will execute one time for each computer in the file. Notice the accent mark at the end of the second line. That character, which is located on the same key as the tilde ~ character on your keyboard, tells PowerShell that the line of code is continued on the next line of the script. In other words, the second and third lines may be two physical lines, but they act as one logical line of script code. The ` character allows the line to be "broken" so it's easier to read-also so that it will fit in this book!
The Get-Wmiobject cmdlet is given the WMI namespace class and computer name. The computer name is in the $computer variable that is populated by the foreach loop. What comes back from Get-Wmiobject is a collection of WMI class instances. In this case, it will only be one instance since you have only one operating system running at a time. However, it's still a collection or list, so we'll store the collection in the $wmi variable.
# Connect to WMI $wmi = get-wmiobject -class "Win32_OperatingSystem" ` -namespace "root\cimv2" -computer $computer
Shortcuts
If you look at the help for the Get-WMIObject cmdlet, you'll notice that the correct parameter name for the remote computer is actually -computername, not -computer. PowerShell actually lets you type as little of the parameter name as possible, provided you've typed at least enough to distinguish it from other parameters. In this case, simply typing -comp would actually have been sufficient.
A foreach loop can enumerate any collection including our collection of one contained in $wmi. Each time through the loop, the variable $item will represent the current item from the collection. In other words, $item represents a single instance of the Win32_OperatingSystem class we retrieved with Get-Wmiobject.
Since $item represents a WMI class, it will have all the properties and methods of that class including the Reboot method. Therefore, we'll call that method and write the name of the computer so we know it has been restarted.
# Restart computer foreach ($item in $wmi) { $wmi.reboot() write-host "Restarted " + $computer } }
This is a good time to ask, "How did you know the Win32_OperatingSystem class had a Reboot method?" More to the point, you might ask, "Where did you find the Win32_OperatingSystem class in the first place?" We could tell you that we read the WMI documentation, which we did - but that's not the answer you probably want to hear. That's okay, because we have another answer. This one involves exploring and using PowerShell. We knew WMI could be used for nearly any kind of computer inventory task. We also knew basic stuff like shutting down and restarting computers could be done. So we needed to get a list of all available WMI classes. No sweat:
PS:> Get-wmiobject -list
We know this is a long list, but it's comprehensive. Browse through it until you see something that looks likely to have what you need. We actually looked at Win32_ComputerSystem first, but it didn't have a Restart or Reboot method. We eventually found the Win32_OperatingSystem, which does have a Restart or Reboot method. Now you might be asking, "How did you discover that?" The answer is pretty simple. First, ask PowerShell to retrieve an instance of the class. Next, using Get-Member, ask PowerShell to display the type information for the class. Get-Member will be discussed in greater detail in Chapter 4.
PS > $wmi = get-wmiobject -class Win32_OperatingSystem -namespace root\cimv2 PS C:\> $wmi | get-member TypeName: System.Management.ManagementObject#root\cimv2\Win32_Ope Name MemberType Defi ---- ---------- ---- Reboot Method Syst SetDateTime Method Syst Shutdown Method Syst Win32Shutdown Method Syst BootDevice Property Syst BuildNumber Property Syst BuildType Property Syst Caption Property Syst CodeSet Property Syst CountryCode Property Syst CreationClassName Property Syst CSCreationClassName Property Syst CSDVersion Property Syst CSName Property Syst CurrentTimeZone Property Syst DataExecutionPrevention_32BitApplications Property Syst DataExecutionPrevention_Available Property Syst DataExecutionPrevention_Drivers Property Syst DataExecutionPrevention_SupportPolicy Property Syst Debug Property Syst Description Property Syst Distributed Property Syst EncryptionLevel Property Syst ForegroundApplicationBoost Property Syst FreePhysicalMemory Property Syst FreeSpaceInPagingFiles Property Syst FreeVirtualMemory Property Syst InstallDate Property Syst LargeSystemCache Property Syst LastBootUpTime Property Syst LocalDateTime Property Syst Locale Property Syst Manufacturer Property Syst MaxNumberOfProcesses Property Syst MaxProcessMemorySize Property Syst Name Property Syst NumberOfLicensedUsers Property Syst NumberOfProcesses Property Syst NumberOfUsers Property Syst Organization Property Syst OSLanguage Property Syst OSProductSuite Property Syst OSType Property Syst OtherTypeDescription Property Syst PlusProductID Property Syst PlusVersionNumber Property Syst Primary Property Syst ProductType Property Syst
This output is a bit truncated to save space. However, basically we retrieved an instance of the class into the variable $wmi, and then piped that variable to Get-Member. That cmdlet's job is to display everything it can find out about an object. In this case, the information includes a full list of properties and methods.
To be sure, we much prefer reading the documentation. In particular, the WMI docs tell you what each method does, which is tremendously helpful.
So that's how our first script works. But let's take a few more minutes and look at some important characteristics of how this script is put together. Here's the complete script one more time:
# Define input parameters param ( [string] $filename = $(throw "Filename is required!") ) Write-Host "Reading computer names from $filename" # Read file $computers = get-content $filename foreach ($computer in $computers) { # Connect to WMI $wmi = get-wmiobject -class "Win32_OperatingSystem" ` -namespace "root\cimv2" -computer $computer # Restart computer foreach ($item in $wmi) { $wmi.reboot() write-host "Restarted " + $computer } }
Notice that the lines starting with # are just comments. PowerShell completely ignores comments. So, comments are just there for our benefit. It's a great idea to thoroughly comment your scripts so that someone else-or yourself, six months later-can figure out what you were thinking.
Also notice how we've used indentation. Within each block, which includes the two foreach loops and the param statement, we've indented the lines just a bit. This indentation visually helps group those lines as a part of one block. When blocks start nesting, such as the second foreach loop within the first, it's easier to tell what code goes together.
Also notice how PowerShell delimits blocks using curly braces. You don't have to put the braces on separate lines as we've done. For example; the following is also legal:
foreach ($item in $wmi) { $wmi.reboot() write-host "Restarted " + $computer }
This is also legal:
foreach ($item in $wmi) { $wmi.reboot() write-host "Restarted " + $computer }
Other variations are legal as well.
The last major thing we want to point out is that PowerShell allows you to put multiple commands that would normally be separate lines in a script, on a single physical line by using a semicolon to separate them:
foreach ($item in $wmi) { $wmi.reboot(); write-host "Restarted " + $computer}
The ; character is nearly the opposite of the ` character since the semicolon tells PowerShell that the second physical line is actually two logical lines of script. We point this out because you're sure to run across it when looking at other folks' scripts and other examples. However, we consider using the ; character to be a poor practice because it makes your scripts harder to read and doesn't offer any real advantage.
With your first PowerShell script out of the way, it's time to start learning more about what's going on under the hood. This process begins in Chapter 3 with we look at PowerShell's security features.