It is currently April 25th, 2024, 9:19 am

Parsing Output of Command Line Programs

Tips and Tricks from the Rainmeter Community
User avatar
Brian
Developer
Posts: 2681
Joined: November 24th, 2011, 1:42 am
Location: Utah

Parsing Output of Command Line Programs

Post by Brian »

There are times when it would be nice to run a command-line program and display parts of its output in Rainmeter. Built-in Windows programs like "ipconfig", "msinfo32", "systeminfo", and "wmic" can provide a wealth of information.

Rainmeter can run a command-line program, but there is no easy way to "capture" the output of the command and use it for some purpose in Rainmeter.


So how do I display the output of these programs in Rainmeter?


The answer is a combination of batch files and redirection operators.

A batch file is a type of script that contains a series of commands to be executed by the command line interpreter (or DOS prompt aka "cmd.exe"). Redirection operators are a nice built-in tool used to redirect the output (or input) from one program (or file) to another program (or file).

The 3 operators I will discuss are:
  • A.exe [b]>[/b] B.txt - Copies the output of "A.exe" and writes it to the file "B.txt". If "B.txt" doesn't exist, it is created, and if "B.txt" already exists, its contents are overwritten.
  • A.exe [b]>>[/b] B.txt - This is similar to the operator above, only instead of overwriting to the contents of "B.txt", it appends the output of "A.exe" to the end of "B.txt".
  • A.exe [b]|[/b] B.exe - Takes the output of "A.exe" and uses it as input to "B.exe"

Ok, so now what?

Perform the following steps in order:
  1. The first thing to do is to make a batch file. Create a new document in "Notepad" or your favorite text editor and save it as "MacAddress.bat". If using Notepad, make sure it does not save the file as "MacAddress.bat.txt".
  2. Next, place the command you want to run on the first line of the .bat file.
    For example:

    Code: Select all

    wmic nic get macaddress,description /format:CSV
  3. The next step is to place the redirection operator ">" and add add a filename to the right side of the operator. Then you append this to the same line as your command. Then you can save the file.
    For Example:

    Code: Select all

    wmic nic get macaddress,description > output.txt
The whole purpose in using a batch file is because Rainmeter doesn't handle the redirection operators very well. In fact it seems to ignore them.


Ok, I have a .bat file, now what?

The batch file is going to make a file called "output.txt", so we need to parse that with Webparser using the "file://" protocol in the URL option. Here's how:
  1. For this example, make sure your .bat file is located in the same directory as your .ini file.
  2. Run the .bat file and then open "output.txt" so that you can see what the output of the command looks like.
  3. Provide the location and RegExp you want to search for inside a Webparser measure.
    For example:

    Code: Select all

    [MacAddress]
    Measure=Plugin
    Plugin=Webparser
    Url=file://#CURRENTPATH#output.txt
    RegExp="(?siU)(?(?=.*Controller).*Controller,(.*)#CRLF#)"
    StringIndex=1
    FinishAction=!DisableMeasure MacAddress
    Disabled=1
    The measure is initially disabled because we need to actually run the command before trying to parse it (which is the next step). In this case (and most cases), you only want to run a command line program on refresh or when a user clicks a button or something, and not every XX seconds. So it is a good idea to immediately disable the webparser measure.
  4. For the command I am using, I only want it to run on refresh, so I will run the batch file with an OnRefreshAction.

    Code: Select all

    [Rainmeter]
    OnRefreshAction=["#CURRENTPATH#MacAddress.bat"][!EnableMeasure MacAddress]

So that's it?

For most command-line programs: Yes, that is all you need to display output from a command-line program.
Unfortunately, programs like "wmic" will sometimes output in non-ansi text, meaning your RegExp will fail. However, you can get around this by redirecting the output of the "wmic" command and use it as input to a command called find.

Since we already know what information we are going to search for in the output.txt file, we can use the "find" command to find that text before we write it to "output.txt" like this:

Code: Select all

wmic nic get macaddress,description /format:CSV | find "Controller" > output.txt
This will use the output of the "wmic" command, and use it as input for the "find" command, then the output of "find" will be written to "output.txt".


So the final code looks like this:

MacAddress.bat:

Code: Select all

wmic nic get macaddress,description /format:CSV | find "Controller" > output.txt
MacAddress.ini:

Code: Select all

[Rainmeter]
DynamicWindowSize=1
OnRefreshAction=["#CURRENTPATH#MacAddress.bat"][!EnableMeasure MacAddress]

[MacAddress]
Measure=Plugin
Plugin=Webparser
Url=file://#CURRENTPATH#output.txt
RegExp="(?siU)(?(?=.*Controller).*Controller,(.*)#CRLF#)"
StringIndex=1
FinishAction=!DisableMeasure MacAddress
Disabled=1

[ShowMacAddress]
Meter=String
MeasureName=MacAddress
FontColor=255,255,255
SolidColor=0,0,0
Text="Your MAC address is: %1"

Advanced:

Running more than one command and combining the output into 1 file.

You can run more than one command in your batch file, just put each command on its own line. Also you can use the ">>" operator to append the results of two command into one file.
For Example:

Code: Select all

wmic nic get macaddress,description /format:CSV | find "Controller" > output.txt
wmic printer where "Default = 'TRUE'" get Caption /format:CSV | find " " >> output.txt
"output.txt" will contain the MAC Address, and then the name of your default printer. Also, notice how I used the "find" command. I did this because "wmic" command likes to output non-ansi text.


Getting rid of the command prompt window.

If you haven't noticed by now, running any batch file within Rainmeter will produce an ugly black window called the command prompt. :vomit: Here is a simple way to get rid of the command prompt window and still run your batch file.
  1. Make a new file and save it as "Something.vbs".
  2. Copy the following command into the .vbs file, and replace "Something.bat" with the name of your batch file. Leave the quotes.

    Code: Select all

    CreateObject("Wscript.Shell").Run "Something.bat", 0, True
    Then save the .vbs file.
  3. Now, in your Rainmeter skin, instead of running your .bat file, run the .vbs file.
    For example:

    Code: Select all

    OnRefreshAction=["#CURRENTPATH#Something.vbs"]
Enjoy!

-Brian
User avatar
MerlinTheRed
Rainmeter Sage
Posts: 889
Joined: September 6th, 2011, 6:34 am

Re: Parsing Output of Command Line Programs

Post by MerlinTheRed »

Nice writeup. Thanks for that :).

I'm curious about this regex: "(?siU)(?(?=.*Controller).*Controller,(.*)#CRLF#)"

What are those nested lookaheads for? Especially the inner one seems kind of redundant. Is this to prevent anything bad from happening if the output of the command doesn't contain the string you're looking for?
Have more fun creating skins with Sublime Text 2 and the Rainmeter Package!
User avatar
Brian
Developer
Posts: 2681
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: Parsing Output of Command Line Programs

Post by Brian »

I pretty much always use look-ahead assertions with my RegExp's just to be safe.

The command I ran in the example outputs something like this: Username, Name of Controller, address. So in my RegExp, I looked ahead for "Controller", and if it is found, then run the regular expression ".*Controller,(.*)". If I didn't include the second "Controller", then the name is returned, not the Mac Address.

-Brian
User avatar
MerlinTheRed
Rainmeter Sage
Posts: 889
Joined: September 6th, 2011, 6:34 am

Re: Parsing Output of Command Line Programs

Post by MerlinTheRed »

(?...) is a non-captured group. In contrast to a lookahead, a non-captured group does consume characters. In your example it doesn't matter though, because you only have one of it.

Since you have the example already set up, could you try if "(?siU)(?(?<Controller,)(.*)#CRLF#)" yields the same result? I'm curious.
Have more fun creating skins with Sublime Text 2 and the Rainmeter Package!
User avatar
Brian
Developer
Posts: 2681
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: Parsing Output of Command Line Programs

Post by Brian »

Nope, I got an error: WebParser.dll: [MacAddress] PCRE compilation failed at offset 21: syntax error in subpattern name (missing terminator)

BTW - I want to point out that I am no expert on regular-expressions.

-Brian
User avatar
MerlinTheRed
Rainmeter Sage
Posts: 889
Joined: September 6th, 2011, 6:34 am

Re: Parsing Output of Command Line Programs

Post by MerlinTheRed »

Nevermind. I'm going to experiment a bit myself. I think I misspelled it and it has to be "(?siU)(?(?=<Controller,)(.*)#CRLF#)".
Have more fun creating skins with Sublime Text 2 and the Rainmeter Package!
User avatar
Brian
Developer
Posts: 2681
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: Parsing Output of Command Line Programs

Post by Brian »

Nope, that gives me: WebParser.dll: [MacAddress] Not enough substrings!

-Brian
User avatar
MerlinTheRed
Rainmeter Sage
Posts: 889
Joined: September 6th, 2011, 6:34 am

Re: Parsing Output of Command Line Programs

Post by MerlinTheRed »

I just noticed that you're using a (?(condition)yes-regex) construct. I didn't really know that before. I couldn't find any shorter way of doing what you do without losing the feature of just returning an empty string if nothing is found.
Have more fun creating skins with Sublime Text 2 and the Rainmeter Package!