Hey, Scripting Guy! How Can I Use Windows PowerShell to Create a Text File with Fixed-Length Lines?

Hey, Scripting Guy! How Can I Use Windows PowerShell to Create a Text File with Fixed-Length Lines?

  • Comments 2
  • Likes

 

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I am trying to learn Windows PowerShell 2.0, but I am having some issues when it comes to string manipulation. I have been a faithful reader of the Scripting Guys for several years, and I have found many of the scripts to be critical in my business environment. I wish I could simply translate the VBScript scripts we use directly into Windows PowerShell and not have to fool with learning the “PowerShell way of doing things.” Why should I have to learn a whole new way of working with strings and files? I mean, if a Windows PowerShell script works, is that not the “PowerShell way?” It seems to me like you are letting a bunch of UNIX guys try to tell me how to administer my Windows servers.

-- RS

 

Hey, Scripting Guy! Answer

Hello RS,

Microsoft Scripting Guy Ed Wilson here. One of the great things about being a Microsoft Scripting Guy is the ability to talk and to interact with customers. I always learn from these interactions. Sometimes I learn that I have not been clear about a particular topic, or I learn that a feature I was not too impressed with has solved a particular need for someone. Many times I learn new techniques, tricks, or tips that make me a better scripter.

I am the first to admit I do not know everything there is to know about VBScript, WMI, ADSI, the .NET Framework, or Windows PowerShell. The topics are too big, and I do not believe that anyone knows everything about scripting. The truly great thing about Windows PowerShell is that you do not need to know everything about Windows PowerShell to write a script that works. This is also both a cool thing and a frustrating thing.

Let me explain a little bit. Many times I spend hours working on a script only to have someone pop up and say, “Why didn’t you do such and such?” Quite often, their suggestion takes my 30-line script and shortens it down to a single line of code. Does this mean my script is “wrong” and theirs is “right”? Not at all. If a script works, it works. If a script solves your problem, it has done its job.

Can a script be made better? In nearly every case the answer is yes. What is the best script? Well, that depends on your criteria. Here are some common criteria for judging scripts:

  • Shortest amount of code
  • Shortest amount of development time
  • Most robust (ability to run in many different environments)
  • Easiest to read
  • Most maintainable
  • Easiest to troubleshoot
  • Most reusable
  • Easiest to use

As you can see, the many ways to judge the best script are almost as varied as the number of different people writing scripts. To directly answer your question, in most cases you can in fact make a Windows PowerShell script seem like a VBScript. If this works for you, by all means go for it. The thing is that in many cases Windows PowerShell has the ability to make things much easier. You mentioned you do a lot of work with strings. Let’s take as an example a Hey, Scripting Guy! Blog post you may remember from the past: “How Can I Make Sure That No Line in a Text File Exceeds 261 Characters?” The VBScript from that article is shown here.

ShortenLinesInTextFile.vbs


Const ForReading = 1 
Const ForWriting = 2 
Set objFSO = CreateObject("Scripting.FileSystemObject") 
Set objFile = objFSO.OpenTextFile ("C:\HSG\hsg62810.txt", ForReading) 
Do Until objFile.AtEndOfStream 
   
strLine = objFile.ReadLine 
   
strLine = Left(strLine, 9) 
   
strContents = strContents & strLine & vbCrLf 
Loop 
objFile.Close 
Set objFile = objFSO.OpenTextFile("c:\scripts\test.txt", ForWriting) 
objFile.Write strContents 
objFile.Close

The script is designed to read a text file that contains lines of varying lengths, and shorten the lines to a specific length (9 characters, in this example). If the line is shorter than 9 characters, that is okay and there is no requirement for line padding. The text file in question is shown in the following image.

Image of text file in question

After the script has run, the text file shown in the following image is created.

Image of text file created after script is run

To translate this script to Windows PowerShell requires very little effort. The key things to remember are that in Windows PowerShell new objects are created by the New-Object cmdlet, and that variables have a dollar sign in front of them. Oh yeah, the Do…Until loop does not have a loop command. One of the key tricks to making a translated VBScript is getting the ability to use the VBScript functions. Windows PowerShell does not have a Left command that is used for string manipulation. It has a substring method from the .NET Framework string class. To gain access to the VBScript string functions, load the microsoft.visualbasic assembly by using the Add-Type cmdlet (in Windows PowerShell 1.0, you used the reflections.assembly .NET Framework class). To make using Left easier, create an alias for the microsoft.visualbasic.strings .NET Framework class. These two commands are shown here:


add-type -AssemblyName microsoft.visualbasic 
$strings = "microsoft.visualbasic.strings" -as [type]

In VBScript, there were two concatenation operators: the + and the &. In Windows PowerShell, there is only the + character. In VBScript, you had the VBCRLF operator; Windows PowerShell uses “`r`n” to accomplish the same thing. To make the script as similar to the VBScript as possible, I created the variable $VbCrLf and set it equal to “`r`n” as shown here:


$VbCrLf = "`r`n"

The complete ShortenLinesInTextFileTransFmVBS.ps1 script is shown here.

ShortenLinesInTextFileTransFmVBS.ps1


add-type -AssemblyName microsoft.visualbasic 
$strings = "microsoft.visualbasic.strings" -as [type] 
$VbCrLf = "`r`n" 
$forReading = 1 
$forWriting = 2 
$objFSO = New-Object -ComObject scripting.filesystemobject 
$objFile = $objFSO.OpenTextFile("C:\HSG\hsg62810.txt", $forReading) 
Do 
{ 
$strLine = $objFile.ReadLine() 
$strLine = $strings::Left($strLine, 9) 
$strContents = $strContents + $strLine + $VbCrLf 
} Until($objFile.AtEndOfStream) 
$objFile.Close() 
$objFile = $objFSO.OpenTextFile("C:\HSG\hsg62810.txt", $forWriting) 
$objFile.Write($strContents) 
$objFile.Close()

Another way to write this script that takes advantage of some of the features of Windows PowerShell is seen in the CreateFileWithLinesOfSpecificLength.ps1 script. I decided to add one improvement to the script, and that is to make a backup copy of the file before modifying the original text file. After assigning the path to the file, I use the Rename-Item cmdlet to rename the original file to hsg62810.txt.old. To do that, I take advantage of the expanding string feature in Windows PowerShell. When a variable is placed inside double quotation marks, it automatically expands to display the value that is contained inside the variable. This makes it really easy to do things such as append new file extensions to original file names. This is shown here inside the Windows PowerShell console where the path is assigned to the $file variable, and then the file with a new extension is displayed. Note that this does not assign a new value to the $file variable, but just displays a string with .old appended to it. This is shown here:


PS C:\> $file = "C:\fso\hsg62810.txt"

PS C:\> "$file.old"

C:\fso\hsg62810.txt.old

PS C:\>

To read the contents of a text file, I like to use the Get-Content cmdlet. Because I renamed the original file to hsg62810.txt.old, I use the string “$file.old” as the path to the file. The Get-Content cmdlet returns an array of strings. This array of strings is sent down the pipeline to the Foreach-Object cmdlet. In the Foreach-Object cmdlet, the if statement is used to check the length of each line as it comes down the pipeline. The $_ automatic variable is used as a placeholder in the Foreach-Object cmdlet to allow us to work with the lines of text as they come down the pipeline. Each line of text is an instance of the system.string .NET Framework class, which has a length property that indicates how many letters are in the string. If the length is less than or equal to (-le operator) 9 characters, the line of text is added to the newly created hsg2810.txt file. The new file will be automatically created by the Add-Content cmdlet. The text that is written to the file is the string represented by the $_ variable. This section of the script is shown here:


ForEach-Object { 
if($_.length -le 9) { add-content -Path $file -Value $_ }

If the line of text is longer than 9 characters, the system.string substring method is used to return the first 9 characters from the line. There are two parameters for the substring method. The first one is the starting position, and the second parameter is the number of characters to return. This is very similar to the VBScript function mid. An example of using the VBScript function mid is shown here:


Dim MyVar

MyVar = Mid("VB Script is fun!", 4, 6) ' MyVar contains "Script".

The same substring command is shown here. You will notice that Windows PowerShell begins numbering at 0 for the starting position:

PS C:\> $myvar = "VB Script is fun!"

PS C:\> $myvar.Substring(4,6)

Script

PS C:\> $myvar.Substring(3,6)

Script

PS C:\>

The cool thing is that because everything in Windows PowerShell is an object, a string is in reality already an instance of the system.string .NET Framework class. This means the substring method is immediately available on any string. You do not even have to put parentheses around the string first. This is shown here:

PS C:\> "VB Script is fun!".substring(3,6)

Script

PS C:\>

The Add-Content cmdlet automatically appends to a text file, and there is no need to add any special parameters to cause this action. The line from the script that selects nine characters from the beginning of the line of text on the pipeline and writes it to the text file is shown here:


{ Add-Content -Path $file -Value $_.Substring(0,9) }

The complete CreateFileWithLinesOfSpecificLength.ps1 script is shown here.

CreateFileWithLinesOfSpecificLength.ps1


$file = "C:\fso\hsg62810.txt" 
Rename-Item -Path $file -NewName "$file.old" 
Get-Content -Path "$file.old" | 
ForEach-Object { 
if($_.length -le 9) { add-content -Path $file -Value $_ } 
Else 
{ Add-Content -Path $file -Value $_.Substring(0,9) } 
}

RS, that is all there is to using Windows PowerShell to create a file with fixed-length lines. String Migration Week will continue tomorrow.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.


Ed Wilson and Craig Liebendorfer, Scripting Guys

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • <p>Love the blog! My need is for MakeCert and the other tools necessary to satisfying win7 security. Anyone have a (working) link? (The programs are NOT on my machine, which is .net4 and VS2008). All help appreciated!</p>

  • <p>Looks like a typo in image url making both &#39;before&#39; and &#39;after&#39; look the same, maybe it should be <a rel="nofollow" target="_new" href="http://img.microsoft.com/library/media/1033/technet/images/scriptcenter/qanda/hsg/2010/june/hey0628/hsg-06-28-10-02.jpg">img.microsoft.com/.../hsg-06-28-10-02.jpg</a> there? ;)</p>