Hey, Scripting Guy! Question

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?

-- CS

Hey, Scripting Guy! Answer

Hello CS,

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 TechEd Presentation.” You see, Craig and I were at TechEd 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:

Image of the Shot Put.txt file


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.

Get-Help about_commonparameters

The eight common parameters are a listed here:

· Verbose

· Debug

· WarningAction

· WarningVariable

· ErrorAction

· ErrorVariable

· OutVariable

· OutBuffer

In addition, if the cmdlet changes system state, it will support two risk mitigation parameters. The risk mitigation parameters are seen here:

· WhatIf

· Confirm

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

Name           

Provider

Alias          

Alias

C              

FileSystem

cert           

Certificate

D              

FileSystem

Env            

Environment

Function       

Function

HKCU            

Registry

HKLM           

Registry

Variable       

Variable

WSMan          

WSMan

 

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:

Get-Help *filesystem*

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[0] 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.String

Name             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[0].Trim() | Out-File "Shot Put A.txt"
$a[1].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:

Image of the Shot Put A.txt file


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.

ScriptingGamesBeginnerEvent3.ps1

$a = GC "shot put.txt" -delimiter "`r`n`r`n"
$a[0].Trim() | Out-File "Shot Put A.txt"
$a[1].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 scripter@microsoft.com. The 2009 Summer Scripting Games wrap-up will continue tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys