Simple scripts in WSH and PowerShell make the last lines first for easy viewing
|To see what happened most recently first in text-based logs, you can use the Microsoft .NET Framework's class in both Windows Script Host (WSH) and PowerShell to create a simple reversal script.|
I occasionally need to reverse the order of the lines in a text file. Reversal lets me see what happened most recently first in logs, and I can even use it to roll back complex changes such as extended file copy and move operations. No tool does this exact job in Windows, but you can use the Microsoft .NET Framework's Stack class in the System.Collections namespace to make your own tool. I'll demonstrate how to use the Stack class for reversal in both Windows Script Host (WSH) and PowerShell; when I'm done, you'll have tools usable in both the cmd.exe and PowerShell console environments.
The Need for LIFO
Let's talk about the way we typically see files and why reversing the order of their lines might be useful. Due to the way files are stored, we typically understand them as streams of data. The first item into the stream is the first out of the stream; the last item put into a stream is always at the tail end of the stream. This first-in, first-out (FIFO) processing model is called a queue. FIFO is the standard way that data is handled in most simple contexts such as reading, writing, and appending to files from a script.
Although the model works well for problems ranging from recording actions in order of occurrence to processing orders in a supermarket checkout line, sometimes you need to look at the last items first. You might be interested in the most recent lines in the Windows Update log, for example. You also might log complex operations, such as moving and renaming batch files, and might want to reverse what was done—which is possible only if you step through the operations in reverse order. In this last-in, first-out (LIFO) processing, the data structure is called a stack. Just like a stack of papers, the last item you push onto a stack is the first one you can pull (or "pop") from the stack.
Reversing the Stream in VBScript
So to put a file's lines in reverse order, a script simply needs to read the file line by line and stack the lines. The file's first line ends up at the end, and the last line ends up first.
The ReverseStream.vbs script shown in Listing 1 is the simplest possible reversing script. It actually reads from standard input, so you must run it with CScript explicitly as its host. You can put a file into the standard input stream for ReverseStream.vbs in a couple ways. The simplest is to redirect input:
Alternatively, you can use the cmd.exe type command to echo the file to the console and then pipe it into the script, like this:type c:\windows\WindowsUpdate.log | cscript reversestream.vbs
You can save the reversed data by redirecting ReverseStream.vbs's output to a file:cscript reversestream.vbs wu-reversed.log
To simplify running the script, you can create a file named ReverseStream.cmd in the same folder as ReverseStream.vbs. The ReverseStream.cmd file must contain the line@cscript //Nologo %~dpn0.vbs %*
You can then just type reversestream instead of cscript reversestream.vbs in commands such as those just shown.
To illustrate what the %~dpn0.vbs expression does, let's look at how it works on my system, where ReverseStream.cmd and ReverseStream.vbs are saved in the C:\apps\bin\scripts folder (which is in my command search path). In a batch file, % followed by a number refers to an element in the command line used to invoke the batch file. The variable %0 means the name used to invoke the batch file. The ~, d, p, and n characters between % and 0 are modifiers. The ~ is supposed to expand the element and remove any surrounding quotes; it also makes the command processor treat any following letters as special modifiers. The d expands to the drive letter for %0, which is C: on my system. The p expands to the relative path for the command, which is \apps\bin\scripts\ on my system. Finally, the n expands to the base name of the command, which is reversestream. So, during runtime, %~dpn0.vbs evaluates to C:, \apps\bin\scripts\, reversestream, and .vbs, forming the expressionc:\apps\bin\scripts\reversestream.vbs
which is the explicit path to the script.
I call ReverseStream.cmd a shadow script, and I discuss this technique a little more in a blog entry that you can link to from the Learning Path. For more information about batch parameters, you can link to the TechNet documentation page listed in the Learning Path.
One other technique that works if you're running the 64-bit version of cmd.exe is to set the default WSH host to CScript by using the commandcscript //h:cscript
This technique won't work if you're running the 32-bit version of cmd.exe. A longstanding flaw of the 32-bit command processor is that it breaks the input pipeline for scripts hosted in external applications such as WSH and Perl.
Let's step through how the script works. For your reference, Table 1 includes some of the significant properties and methods of the .NET System.Collections.Stack class. I use only a couple of these, but the others are handy to know about if you use a stack in other situations.
In the ReverseStream.vbs script in Listing 1, the code at callout A simply creates a System.Collections.Stack object. Even though Stack is a .NET class, it's visible to COM scripting by name. A subset of .NET classes is available for scripting. The "Which .NET Classes Can Be Used from Scripts?" sidebar outlines rules of thumb for administrators to use in finding COM-usable .NET classes and methods.
The next step is to stack the items. At callout B in Listing 1, the script reads lines as long as input is available, and pushes each line onto the stack. I could have used the Pop() method to pop items off the stack until the stack is empty. If I had used that approach, it would look like this:Do While Stack.Count > 0 WScript.StdOut.WriteLine Stack.Pop Loop
However, there's a shortcut. The script can put the stack into an array using ToArray and then join the items by using line endings—which is what the script does at callout C in Listing 1. This approach not only uses shorter code but also reduces the time and processing required for larger stacks.
The PowerShell Version
The PowerShell version of ReverseStream.vbs, Reverse-Stream.ps1 in Listing 2, works similarly. You can use it in any PowerShell pipeline to reverse the items fed to it. You use the Get-Content cmdlet to get the contents of a file:get-content c:\windows\WindowsUpdate.log | reverse-stream
In this statement, you can use gc or type instead of get-content because gc and type are built-in aliases for the Get-Content cmdlet.
The code for the Reverse-Stream.ps1 script is analogous to the VBScript version but even simpler. Note that if you don't have the Reverse-Stream.ps1 script saved to a directory in your Windows search path, you'll need to specify the full path to the script instead of just its name.
The begin clause (which always runs when a script starts up) creates the stack object $stack. Each time an object is read from the input stream, the process clause runs and the object is passed in as $_. All the process clause does is push the object onto the stack.
After the script reaches the end of the input, the end code runs. The script doesn't need to bother with popping the stacked items off the stack or putting them into an array. PowerShell recognizes .NET collections like the Stack object and takes care of it for us. All the script needs to do is push $stack out, and the breakdown is automatically displayed.