Weekend Scripter: Pick Comments from a PowerShell Script

Weekend Scripter: Pick Comments from a PowerShell Script

  • Comments 2
  • Likes

Summary: Guest blogger, Bob Stevens, shares a script to pick out comments from a Windows PowerShell script.

Microsoft Scripting Guy, Ed Wilson, is here. Today we have a new guest blogger, Bob Stevens. I made Bob’s virtual acquaintance recently when I did a Live Meeting presentation to the Twin Cities PowerShell User Group.

Here is Bob’s contact information:

Blog: Help! I’m Stuck in My Powershell!
Twitter: @B_stevens6
LinkedIn: Robert Stevens

The floor is yours, Bob…

As a local Help Desk technician, I run into many repetitive support tasks. From low toners to Internet Explorer issues, I have done it all. About three months ago I discovered that I could use PowerShell to automate a number of these tasks, thereby freeing my time for some of the more unusual issues. Fortunately, all of the computers at my site have Windows PowerShell 2.0 installed, so it was a matter of ensuring that they can run scripts. This can be done by changing the Set-ExecutionPolicy cmdlet to Unrestricted in the following manner:

Set-ExecutionPolicy Unrestricted

When the work is done, switch it back to the organization default with:

Set-ExecutionPolicy Remote-Signed

Set-ExecutionPolicy governs which scripts can be run on any given system. By setting it as Unrestricted, I am removing all restrictions. I switch it back to Remote-Signed to prevent users from running scripts that can potentially damage a system, thereby presenting a potential for data loss.

Fast forward three months and multiple scripts later…

It dawned on me that I would need to create documentation for each of these scripts. Documentation provides my coworkers with the insight they need to understand the purpose and functionality of my work and to pick up where I left off should the script need to be altered. Thankfully, like a good Windows PowerShell scripter, I commented liberally throughout my scripts to ensure that I knew where I would need to alter it in the future. To this end I started working on a script to pull comments. I knew that I habitually use single line comments for documentation and block comments for commenting out blocks of code. Happy accident because the script I devised only pulls the first and the last lines of a block comment.

Note   For simplicity, I name all directories a variation of “foo” and all input files a variation of “foo.*”. You can set foo as whatever you want.

As usual I started with setting my location:

Set-Location “C:\foo”

Next we set our source script file as a variable. I use variables in my scripts to enable me to change one line of code, rather than five lines, thereby reducing the chance of an error. Variables are defined by the dollar sign ($).

$script = “foo.ps1”

We need to define an output variable as $out. Note that I used the $script variable in the variable value. This will result in the file name of foo.ps1 comments.txt. This is to differentiate between output files. For this to work, there must be a space between $script and comments.txt:

$out = “$script comments.txt”

Now that we have defined the output file name, we need to create the output file itself, and I do this with the New-Item cmdlet. Of course, we need to define the object as a file—otherwise we get a dialog box asking us if it’s a file or folder:

New-Item “out” -ItemType File

Now our preparation is complete:

Set-Location "c:\foo"

$script = "foo.ps1"

$out = "$script comments.txt"

New-Item "$out" -ItemType File

We need to pull the content of our source file with the Get-Content cmdlet:

Get-Content $script

The next line is to prevent Windows PowerShell from appending the output to output that already exists in the variable by creating an array, this is done with the array (@) operator, followed by both parentheses:

$comments = @()

This is where the magic takes place. We need to use the Select-String cmdlet. This command requires two parameters: Pattern and File. Both values need to be quoted if you are not using a variable to define it. As we are searching for comments, we are going to select strings that contain “#”:

Select-String -Pattern “#” $script

This is a bit more complex, and it requires stringing three commands together, so we use the pipe (|) operator. The pipe operator merely states do “this” and then do “that” with the output of “this.” Pipe operators must always have a space preceding and succeeding it:

Select-String -Pattern “#” $script |

For our purposes, we are going to say, “For each object, do this.” Coincidentally (or not), Microsoft decided to add a Foreach-Object cmdlet for just this purpose! And everything that you are doing with Foreach-Object must be in braces to group them together:

Select-String - Pattern “#” $script |

Foreach-Object {}

When we string commands like this together, formatting is important—not for functionality, but for readability. Now we need to comment the full line where “#” appears. It is important to note that you must use “+=”, or you will end up with an empty file:

Select-String - Pattern “#” $script |

Foreach-Object {

$comments += $_.line

}

We need to tell Windows PowerShell to grab everything after “#” in that string. This is tricky, but it can be done with the context.postcontext definition:

Select-String - Pattern “#” $script |

Foreach-Object {

$comments += $_.line

$comments += $_.context.postcontext

}

We now need to tell Windows PowerShell to extract everything that we just defined within the Foreach-Object curly brackets:

Select-String - Pattern “#” $script |

Foreach-Object {

$comments += $_.line

$comments += $_.context.postcontext

}

$comments

Finally, we dump our output into the output file $out with the Set-Content cmdlet:

Select-String - Pattern “#” $script |

Foreach-Object {

$comments += $_.line

$comments += $_.context.postcontext

}

$comments | Set-Content $out

The complete script looks something like this:

Set-Location "c:\foo"

$script = "foo.ps1"

$out = "$script comments.txt"

New-Item "$out" -ItemType File

Get-Content $script

$comments = @()

Select-String -Pattern "#" $script |

Foreach-Object {

$comments += $_.line

$comments += $_.context.postcontext

}

$comments | Set-Content $out

The result should take the following input:

Image of script

And give you the following output (Select-String -pattern “#” shows up because it contains a “#”):

Image of command output

This saved me an hour now and countless future hours that I would spend extracting comments for documentation.

When I was verifying my work, I ran across the following note to myself stating that I referenced a Rob Campbell in another script. Some of the code made it in here.

Rob Campbell, Mjolinor: How do you extract data from a txt file with powershell. As always, input is always appreciated.

Thank you for your time. I uploaded the complete script to the Script Center Repository: Extracting Comments from a Script with PowerShell.

~Bob

Thank you, Bob, for sharing your script and your insight with us today. Join us tomorrow when Bob talks about a script he wrote to clean up user profiles. It is cool, and you do not want to miss it.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • For the next iteration of this, you could make the "comment catching" more sophisticated.  One step would be to use regular expressions to change the search pattern from:

      -pattern "#"

    to:

      -pattern "^\s*#"

    This says: "Start at the beginning of the line (^). Match white space (\s) - 0 or more characters (*) of white space, to be precise.  Then match a # character."

    If you do that, then you won't catch lines that have a # in the middle of the line somewhere, which aren't really comments.  Good.  But my revision has the drawback that it won't catch comments that don't start at the beginning of the line - if the comment comes on the same line but after some PowerShell code, the script won't consider it a comment.

    Even that problem can be fixed by some more detailed use of regular expressions, but this is enough complication for now.

  • How to get comments only from PowerShell scripts.

    $g=cat c:\scripts\WindosActivationStatus.ps1

    [system.management.automation.psparser]::Tokenize($g,[ref]$errors)|?{$_.Type -eq 'Comment'}

    This extracts all comments no matter where they occur.

    You can also extract every component of a script and selectively execute any part as needed.  Position info is available by component.