Learn about Windows PowerShell
Hey Scripting Guy! I really enjoyed participating in the 2009 Summer Scripting Games. The events were really cool, and I learned a lot. One minor criticism is in order: I thought the shot put beginner event 3 was too hard. I think it would have been better as an advanced event. What were you guys thinking?
Microsoft Scripting Guy Ed Wilson here. I will tell you exactly what I was thinking when I wrote the description for the 2009 Summer Scripting Game shot put beginner event 3: “Dude don’t let me mess up when I make my Tech∙Ed Presentation.” You see, Craig and I were at Tech∙Ed 2009 where we manned the Scripting Guys booth during the day, had meetings with various MVPs in the evening, and I was reviewing my presentation. In the intervening time, I wrote the descriptions for all the 2009 Summer Scripting Games events so that I could get them e-mailed out to the nearly 40 different guest commentators for the Scripting Games. Needless to say, there was a lot going on. Add to this that I did not bring my tea pot (and was therefore reduced to drinking tea from tea bags and paper cups), and it was a traumatic time. (Is there a therapist in the house?) To be honest, the games had all been planned out before I arrived in Los Angeles, and I had already written sample solutions, so I cannot blame chaos for any lack of skill-leveling that may have been present in any particular event. The basic fact of the matter is that when I write a solution to an event, one approach to the event may be easier than another.
This week we will be reviewing some of the scripts that were submitted during the recently held 2009 Summer Scripting Games. The description of the 2009 Summer Scripting Games details all of the events. Each of the events was answered by a globally recognized expert in the field. There were some cool prizes and winners were recognized from around the world. Additionally, just like at the "real Olympics," because there was a lot going on, an "if you get lost page" was created. Communication with participants was maintained via Twitter, Facebook, and a special forum. The special forum has been taken down, but we still use Twitter and Facebook to communicate with Hey, Scripting Guy! fans. We will be focusing on solutions that used Windows PowerShell. We have several good introduction to Windows PowerShell Hey, Scripting Guy! articles that you will find helpful.
The solution that was submitted by stahler is a case in point. The stahler submission is elegant in its simplicity, but it also illustrates a very good use of an important Windows PowerShell concept. The details for the beginner event 3 provide the details of the scenario. The task was to extract two paragraphs from the “shot put.txt” file and store the first paragraph in a text file named “Shot Put A.txt” and the second paragraph in a file named “Shot Put B.txt.” The “Shot Put.txt” file is seen here:
The most important part of the ScriptingGamesBeginnerEvent3.ps1 script that was submitted by stahler is the first line. The command GC is an alias for the Get-Content cmdlet. What is interesting is that stahler uses an “undocumented” parameter. Undocumented, you may ask? OK, not really, but certainly a “lightly documented” parameter. If you use the Get-Help cmdlet to retrieve help for the Get-Content cmdlet, you will not find the delimiter parameter. The syntax of the Get-Content cmdlet is seen here:
Get-Content [-LiteralPath] <string> [-Credential <PSCredential>] [-Exclude <string>] [-Filter <string>] [-Force] [-Include <string>] [-ReadCount <Int64>] [-TotalCount <Int64>] [-UseTransaction] [<CommonParameters>]Get-Content [-Path] <string> [-Credential <PSCredential>] [-Exclude <string>] [-Filter <string>] [-Force] [-Include <string>] [-ReadCount <Int64>] [-TotalCount <Int64>] [-UseTransaction] [<CommonParameters>]
Nowhere does the delimiter parameter appear in the syntax output. Perhaps it is a common parameter. To find information about the common parameters, use the command seen here.
The eight common parameters are a listed here:
In addition, if the cmdlet changes system state, it will support two risk mitigation parameters. The risk mitigation parameters are seen here:
Ok, so where is the delimiter parameter? It is not a common parameter, and it is not defined on the Get-Content cmdlet. The delimiter parameter is what is called a dynamic parameter. Unfortunately, there is no about_dynamic_parameters help article. So where do you find out about dynamic parameters? To know where to find dynamic parameters, you need to know what a dynamic parameter is.
First a little bit of background. The Windows PowerShell cmdlets have names such as Get-Item or Set-Item because the information to be retrieved or modified can be anything. The item that is exposed depends on the underlying Windows PowerShell provider. If you are working on a drive named C: or D:, the drive is exposed by the Windows PowerShell FileSystem provider. If the drive is named HKLM: or HKCU:, the drive is exposed by the Registry provider. If the drive is Env:, the drive is exposed by the Environment provider. A listing of Windows PowerShell providers and the associated drives is seen in Table 1.
Table 1 Windows PowerShell 2.0 providers and drives
When the Get-Content cmdlet is used on a drive that is supplied by the FileSystem provider, it adds the dynamic parameter delimiter. If the Get-Content cmdlet is used on any Windows PowerShell drive, the delimiter parameter is not available. This information is documented in the help article on the FileSystem provider. You can find information about dynamic parameters by looking at the help article about the specific provider. For example, to find information about dynamic providers available on drives supplied by the FileSystem provider, you can use the command seen here:
After you know the secret of dynamic parameters and know that the Get-Content cmdlet has a delimiter parameter, the rest of the stahler submission is not too complicated. The delimiter used is `r`n`r`n, which is equivalent to a carriage return (`r) and a line feed (`n) done twice (`r`n`r`n). The two carriage returns and line feeds match the spacing between the two paragraphs in the text file. The first parameter for the Get-Content cmdlet is the path parameter. I prefer to write this line of code as seen here (because it makes it easier for me to read):
$a = Get-Content –path “Shot Put.txt” –delimiter “`r`n`r`n”
In general I would use a better variable name than $a (something like $textContent) to make the script easier to understand.
The Trim method is used to remove empty space from the beginning and from the end of the string. That is contained in each element of the array of text that is stored in the $a variable. $a is used to refer to the first paragraph in the $a variable. The Trim method is available because the data stored in the $a variable is a string. When you use the Get-Member cmdlet with a string, you find all the methods that are available. This is seen here:
PS C:\> "string" | Get-Member -MemberType method TypeName: System.StringName MemberType Definition---- ---------- ----------Clone Method System.Object Clone()CompareTo Method int CompareTo(System.Object value), int CompareTo(string strB)Contains Method bool Contains(string value)CopyTo Method System.Void CopyTo(int sourceIndex, char destination, int destinationIndex, int count)EndsWith Method bool EndsWith(string value), bool EndsWith(string value, System.StringComparison compari...Equals Method bool Equals(System.Object obj), bool Equals(string value), bool Equals(string value, Sys...GetEnumerator Method System.CharEnumerator GetEnumerator()GetHashCode Method int GetHashCode()GetType Method type GetType()GetTypeCode Method System.TypeCode GetTypeCode()IndexOf Method int IndexOf(char value), int IndexOf(char value, int startIndex), int IndexOf(char value...IndexOfAny Method int IndexOfAny(char anyOf), int IndexOfAny(char anyOf, int startIndex), int IndexOfA...Insert Method string Insert(int startIndex, string value)IsNormalized Method bool IsNormalized(), bool IsNormalized(System.Text.NormalizationForm normalizationForm)LastIndexOf Method int LastIndexOf(char value), int LastIndexOf(char value, int startIndex), int LastIndexO...LastIndexOfAny Method int LastIndexOfAny(char anyOf), int LastIndexOfAny(char anyOf, int startIndex), int ...Normalize Method string Normalize(), string Normalize(System.Text.NormalizationForm normalizationForm)PadLeft Method string PadLeft(int totalWidth), string PadLeft(int totalWidth, char paddingChar)PadRight Method string PadRight(int totalWidth), string PadRight(int totalWidth, char paddingChar)Remove Method string Remove(int startIndex, int count), string Remove(int startIndex)Replace Method string Replace(char oldChar, char newChar), string Replace(string oldValue, string newVa...Split Method string Split(Params char separator), string Split(char separator, int count), st...StartsWith Method bool StartsWith(string value), bool StartsWith(string value, System.StringComparison com...Substring Method string Substring(int startIndex), string Substring(int startIndex, int length)ToCharArray Method char ToCharArray(), char ToCharArray(int startIndex, int length)ToLower Method string ToLower(), string ToLower(System.Globalization.CultureInfo culture)ToLowerInvariant Method string ToLowerInvariant()ToString Method string ToString(), string ToString(System.IFormatProvider provider)ToUpper Method string ToUpper(), string ToUpper(System.Globalization.CultureInfo culture)ToUpperInvariant Method string ToUpperInvariant()Trim Method string Trim(Params char trimChars), string Trim()TrimEnd Method string TrimEnd(Params char trimChars)TrimStart Method string TrimStart(Params char trimChars)
The Out-File cmdlet is used to create a text file. The first parameter for it is the filepath parameter. The second and the third lines of the script are seen here:
$a.Trim() | Out-File "Shot Put A.txt"$a.Trim() | Out-File "Shot Put B.txt"
When the script runs, the two text files are created. The “Shot Put A.txt” file is seen here:
The Rename-Item cmdlet is used to rename the original “shot put.txt” file to “shot put.old.” The complete ScriptingGamesBeginnerEvent3.ps1 script is seen here.
$a = GC "shot put.txt" -delimiter "`r`n`r`n"$a.Trim() | Out-File "Shot Put A.txt"$a.Trim() | Out-File "Shot Put B.txt"Rename-Item "shot put.txt" "shot put.old"
If you want to be the first to know what is happening on the Script Center, follow us on Twitter or on Facebook. If you need assistance with a script, you can post questions to the Official Scripting Guys Forum, or send an e-mail to firstname.lastname@example.org. The 2009 Summer Scripting Games wrap-up will continue tomorrow. Until then, peace.
Ed Wilson and Craig Liebendorfer, Scripting Guys