Template Information

Trang

An Introduction To PowerShell

Thứ Bảy, 15 tháng 10, 2011 / 01:25

Overview

Windows PowerShell is a new command-line shell and scripting language created by Microsoft. Why yet another shell, you ask? Well, PowerShell is different. Of course, every new shell claims to be "different." However, few, if any, live up to this claim, but PowerShell has some features that truly distinguish it from other shells. In this article, you will look at some of the PowerShell scripting language features and create a PowerShell script from scratch.

A Brief History of the Windows Command-Line Shell

Since the introduction of Windows NT, CMD.EXE has been the command-line shell for Windows. Although CMD.EXE provided an improved command-line experience over its DOS predecessor, COMMAND.COM, it still relied on a relatively primitive scripting language, Windows Command (.CMD and .BAT) files. The later addition of Windows Scripting Host, and the VBScript and JScript languages, greatly improved the scripting capabilities of the shell.
These technologies combined to form a fairly comprehensive command-line shell and scripting environment. There is not really a question of how much you can accomplish with CMD.EXE, .CMD files, and Windows Scripting Host. Rather, the primary complaint is the proverbial "hoops to jump through" to accomplish some seemingly simple task.
Using this "framework" of command-line and scripting tools, any moderately complex script requires a mix of batch commands, Windows Scripting Host, and stand-alone executables. Each of these, in turn, use different conventions for execution and requesting, parsing, and returning data.
Weak variable support in CMD.EXE, inconsistent interfaces, and limited access to Windows settings combined with one other weakness make command-line scripting more difficult than it has to be. What is the one other weakness, you ask? Text. With these technologies, everything is text. The output of one command or script is text and must be parsed and re-formatted to work as input for the next command. This is where PowerShell makes a radical departure from all traditional shells.

PowerShell Scripts == Batch Files on Steroids


PowerShell itself is written in a .NET language and relies heavily on the .NET Framework. Because of this, PowerShell was designed from the ground up as an object-oriented shell and scripting language. Everything in PowerShell is an object and has the full capabilities of the .NET Framework. A command returns a set of objects that can then utilized by using the properties and methods of that type of object. When you want to pipe the output of one command into another command, PowerShell actually passes the objects, not just the textual output of the first command. This gives the next command in the pipeline full access to all of the objects properties and methods.
The treatment of everything as an object, and the ability to pass objects between commands is a major shift of philosophy for command-line shells. That said, PowerShell still functions much like a traditional shell. Commands, scripts, and executables can be typed and ran from the command-line and the results are displayed in text. Windows .CMD and .BAT files, VBScripts, JScripts, and executables that work in CMD.EXE all still run in PowerShell. However, because they are not object-oriented, they do not have full access to the objects created and used in PowerShell. These legacy scripts and executables will still treat everything as text, but you can intermingle PowerShell with these technologies. This is very important if you want to start using PowerShell, but have a complex set of existing scripts that you are not able to convert all at once.

A PowerShell Script

Reading about great new technology is one thing, but seeing it and using it makes comprehension easier. In the rest of this article, you will develop a PowerShell script as a demonstration of PowerShell's capabilities and ease of use.
DIR is one of the most-used command in CMD.EXE. It lists the files and folders contained in a folder, as shown in Figure 1. Along with the names of each, it shows the last modified date and time, and the size of each file. DIR also shows the combined size of all the files in the specified folder, as well as the total number of files and the total number of sub-folders.

(Full Size Image)
Running DIR in PowerShell also produces a directory listing. However, as Figure 2 shows, it looks a little different. PowerShell does not have a native command called DIR, but it does have one called Get-ChildItem, which performs a similar function. In PowerShell, DIR is an alias for Get-ChildItem; I am not going to cover aliases in this article, but you can think of DIR in PowerShell as a shortcut for Get-ChildItem.
DIR in PowerShell provides much of the same information: a file and folder listing, the last modified date and time, and the size of each file. However, it is missing the summary information that DIR in CMD.EXE provides: the total size of all the files in the folder, the total number of files, and the total number of sub-folders.

(Full Size Image)
For your script, you will create a PowerShell script that will mimic the CMD.EXE DIR command. In the next section, I will explain the key parts of the script.

DIR.PS1: The Header

A PowerShell script consists of PowerShell commands in a plain text file with a .PS1 extension. For your DIR replacement, you will use a text file called DIR.PS1.
To run the script, type the following at a PowerShell prompt:
.\DIR.PS1 X:\Folder
where X is a drive letter and Folder is the name of a folder.
The first part of your script will display the same header information that the CMD.EXE DIR command produces. You need to determine the drive letter of the path that was passed into the script. Arguments to scripts are stored in an array called $args (all PowerShell variables begin with a $). To keep things simple for now, you are only going to deal with the first argument that was passed into your script and ignore the rest. Therefore, you will create a variable called $drive and set it equal to $args[0] (0 is the first element of all arrays in PowerShell).
$drive = $args[0].SubString(0, 1).ToUpper()
To get some of the information you need about the drive, you will have to utilize Windows Management Instrumentation (WMI). The details of WMI is outside of the scope of this article, but the following PowerShell code is pretty easy to understand. You create a variable, $filter, to use with the Get-WmiObject command. The filter you have created tells the command that you only want information about a particular disk. The results of the Get-WmiObject command are stored in a variable called $volInfo. Remember, in PowerShell everything is an object; $volInfo is now an object returned from Get-WmiObject.
$filter = "DeviceID = '" + $drive + ":'"
$volInfo = Get-WmiObject -Class Win32_LogicalDisk -Filter $filter
You now have access to all of the properties and methods associated with this object. The serial number of the drive can be accessed via the VolumeSerialNumber property. The serial number is returned as an 8-character string, but you want to format it as the first 4 characters followed by a dash, and then the last 4 characters. In the line below, the backtick character at the end of the first line is the PowerShell line continuation character. Basically, it just tells PowerShell that the line continues on the next line. Splitting the line is not required, but I did it here to decrease the width of the line and to improve the readability of the script.
$serial = $volInfo.VolumeSerialNumber.SubString(0, 4) + "-" + `
$volInfo.VolumeSerialNumber.SubString(4, 4)
Now that you have the $volInfo object, you can write the DIR header information to the screen. If a volume has no label, the text written to the screen is slightly different than if the volume does have a label. A simple If-Else statement is used to determine whether the VolumeName property is equal to an empty string. The Write-Host command is used to write each line to the screen.
If ($volInfo.VolumeName -eq "")
{ Write-Host (" Volume in drive " + $drive + " has no label") }
Else
{ Write-Host (" Volume in drive " + $drive + " is " +
$volInfo.VolumeName) }
Write-Host (" Volume Serial Number is " + $serial)
Write-Host ("`n Directory of " + $args[0] + "`n")
The "`n" at the beginning and end of the last Write-Host command is used to insert a newline before and after the text. The Write-Host command adds a newline at the end of each line itself, so the effect of the "`n" is to make a blank line before and after that line of text.
Did you notice the "-eq" in the If statement? That is the equality comparison operator. The table below shows all of the comparison operators:
-eq, -ieq Equals
-ne, -ine Not equals
-gt, -igt Greater than
-ge, -ige Greater than or equal
-lt, -ilt Less than
-le, -ile Less than or equal
The -i versions of the comparison operators are the case-insensitive versions.
Figure 3 shows the output of the script you have so far.

DIR.PS1: The File/Folder Listing

Now, you are ready to display the contents, and their properties, of this folder. The first thing you will do is call the PowerShell Get-ChildItem command with the folder name passed into the script as a parameter. The Get-ChildItem command will get the collection of file and folder objects, not just names, and then pipe those objects directly into the Sort-Object command to sort them. By default, the Sort-Object command will sort based on the Name property of the objects, so you do not need to specify any additional parameters. The sorted collection of objects will then be stored in the variable named $items.
$items = Get-ChildItem $args[0] | Sort-Object
Once you have a collection of file and folder objects, you need to loop through them and display the appropriate properties. To loop through the objects, you use the ForEach command. For each file or folder in your $items collection, you will get the last modified date and time, the name, and the length or size file. The strange looking strings inside the curly brackets are .NET string formatting codes. I use them to easily right- or left-align fields and format dates, times, and numbers. Understanding the string formatting code is not crucial to understanding this script.
The If statement is where you determine if an item is a directory. If the first character of the Mode property equals "d", the item is a directory. You need to check because what you write to the screen for directories is different than what you write for files.
Notice the $totalDirs++ line inside the If statement. It is the counter that keeps track of the number of directories in this directory. Similarly, there is a $totalFiles variable that is used to track the number of files and a $totalSize variable that tracks the total size of all of the files. These values are computed now, but you will not display them until the end of the file listing.

ForEach ($i In $items)
{
$date = "{0, -20:MM/dd/yyyy hh:mm tt}" -f $i.LastWriteTime
$file = $i.Name
If ($i.Mode.SubString(0, 1) -eq "d")
{
$totalDirs++
$list = $date + " {0, -15}" -f "
" + " " + $file
}
Else
{
$totalFiles++
$size = "{0, 18:N0}" -f $i.Length
$list = $date + $size + " " + $file
}
$totalSize += $i.Length
Write-Host $list
}
Figure 4 shows the output of the script of the updated script.

(Full Size Image)

DIR.PS1: The Footer

The only thing left to do is write to the screen the total count of files and directories, the total size of the files, and the available free space on this drive. To do this, you will utilize the counter variables ($totalFiles, $totalDirs, $totalSize) that you created in the previous section of the script and you can get the available free space from the $volInfo variable that you created back at the beginning of the script.
Write-Host ("{0, 16:N0}" -f $totalFiles + " File(s)" + `
"{0, 15:N0}" -f $totalSize + " bytes")
Write-Host ("{0, 16:N0}" -f $totalDirs + " Dir(s)" + `
"{0, 16:N0}" -f $volInfo.FreeSpace + " bytes free`n")
Figure 5 shows the complete output of the script.

(Full Size Image)

Caveats and Possible Improvements

Although the script you created produces nearly identical output to the CMD.EXE DIR command, there are a few caveats to be aware of and some possible improvements you could make.
  • This script does not perform any error checking.
  • If a valid path is not passed into the script, the script will fail with a PowerShell error message.
  • The directory count displayed by the script is 2 less than the CMD.EXE DIR command because the Get-ChildItem command does not include the "." and the ".." directories like the CMD.EXE DIR does.
  • Your script sorts by file/folder name by default and does not offer any way to sort by any other attribute.
  • Your script does not have the ability to display the contents for a folder and all of the sub-folders.

Conclusion

Although PowerShell is a powerful shell and scripting language, it can take a little time to really grasp it, especially if you are not already familiar with the .NET Framework. I hope that this article and script will be useful to anyone trying to learn about PowerShell. Even though the script that you created is fairly simple, I believe it can serve as a good foundation for building upon.

0 nhận xét:

Đăng nhận xét