Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Re: unpacking wmic command's unicode output

by goibhniu (Hermit)
on Nov 12, 2008 at 17:25 UTC ( [id://723220]=note: print w/replies, xml ) Need Help??


in reply to unpacking wmic command's unicode output

I learned some things while working on this little problem, so I thought I'd share. update: changed $ARGV[0] for @ARGV per discussion.

The first thing is that wmi doesn't necessarily use the same fixed headers on different machines. My first attempt was a simple batch file and worked on my machine:

@echo off wmic process |find /i "Caption" |perl -CS -ne "($caption,$commandline, +$ppid,$pid)=unpack('a[21] a[270] @798a[17] @899a[11]',$_);print '}',$ +caption,'-',$ppid,'-',$pid,'-',$commandline,'{',$/;" |find /i "parent +" wmic process |find /i "%1" |perl -CS -ne "($caption,$commandline,$ppid +,$pid)=unpack('a[21] a[270] @798a[17] @899a[11]',$_);print '}',$capti +on,'-',$ppid,'-',$pid,'-',$commandline,'{',$/;" |find /v /i "find"

but not on either the machine of the admin I was doing this for or on the prod machine he needed it for. The static unpack template was off for the set of fields wmi was using on the other machines (to be semantically honest I didn't investigate enough to blanme this on wmi; it was just off).

So, I ended up turning it inside-out. Instead of using perl in a batch file I used the wmic command in a perl script. I ended up opening a file (well, pipe) like almut suggested, but using BrowserUK's command line option to cover the unicode issue.

#!/usr/bin/perl -WCD use strict; use warnings; use Data::Dumper; $\ = $/; my $debug = 1; #array of fields to display my @processFields = ('Caption','ParentProcessId','ProcessId','CommandL +ine'); #ARGV processing #my $searchfor = $ARGV[0] ? join(' ',@ARGV) : die("I need a process to + look for."); # corrected, per bug in discussion below. You might also check out th +e style discussion not changed here. my $searchfor = @ARGV ? join(' ',@ARGV) : die("I need a process to loo +k for."); #set up handle - note -CD arg, above open (PROCINFO, "wmic process |")||die("Can't open wmic for process in +fo pipe!"); #loop my $template=''; while (<PROCINFO>) { #first time get header, find position of columns, build unpack tem +plate unless ($template) { while (@processFields) { my @allFields = split; my $field = pop @processFields; my $fieldIndex; for ($fieldIndex=0; $fieldIndex<=$#allFields; $fieldIndex+ ++) { if ($allFields[$fieldIndex] eq $field) { last; } #assert: found $field in @allfields with index $fieldI +ndex } pos=undef; /\b$field\b/g or die("Could not find field $field"); my $fieldPos = pos; $fieldPos = $fieldPos - length($field); my $nextFieldPos; if ($fieldIndex+1 <= $#allFields) { pos=undef; /\b$allFields[$fieldIndex+1]\b/g or die("Could not fin +d next field $allFields[$fieldIndex+1]"); $nextFieldPos = pos; $nextFieldPos = $nextFieldPos - length($allFields[$fie +ldIndex+1]); } else { $nextFieldPos = length($_)+1; } my $fieldlength = $nextFieldPos - $fieldPos; $template = '@'.$fieldPos.'a['.$fieldlength.'] '.$template +; } #do it once for the header row my $fieldVal; my $outLine=''; foreach $fieldVal (unpack($template, $_)) { $outLine .= ($outLine?'-':'}').$fieldVal; } $outLine .= '{'; print $outLine; #all other times look for, unpack and display if matches arg } if (/$searchfor/) { my $fieldVal; my $outLine=''; foreach $fieldVal (unpack($template, $_)) { $outLine .= ($outLine?'-':'}').$fieldVal; } $outLine .= '{'; print $outLine; } } close(PROCINFO);

If I were to spend more time on it I would get the common code (to get the position of a field and also to constuct the output lines) out into functions. Also, I've rethunk how to get the length of the fields by looking for something like /\G(\b)/gc, but it's a fairly strightforward little throw-away script, so I'm not going to spend that much more time on it.

Back to lessons learned:
- if you're searching for 'ProcessID' you'll find it in 'ParentProcessID' (duh!); this messed me up for a little bit and drove me to put all that $nextField logic in (again, I've rethunk it and think it would be more elegant with \G).
- -CS worked on the command line, but when I added it to the she-bang (#!/usr/bin/perl -CS -W) it complained about an 'Unknown Unicode option letter'. I finally figured out I had to combine things in the command options as #!/usr/bin/perl -WCS. In retrospect, there are other problems in my past that are explained by this and I never knew it.
- I had to pos=undef; to get the search to reset, since it didn't search backwards from the previous match.
- $#ARGV is 0 whether there are zero or 1 arguments. This led me to the $ARGV[0] ? : solution.
- I was worried about getting the output fields in the order I want and was happy to learn that using @location in the template let me bounce around in the string and still return the fields in the order they appreared in the template.

I'm sure there are other lessons here and many wasy to improve my little script, but as I said, I probably won't spend any more time on it (the script that is; I'll happily spend time learning form any feedback I get here). I hope the lessons learned are helpful.


#my sig used to say 'I humbly seek wisdom. '. Now it says:
use strict;
use warnings;
I humbly seek wisdom.

Replies are listed 'Best First'.
Re^2: unpacking wmic command's unicode output
by ikegami (Patriarch) on Nov 12, 2008 at 17:58 UTC

    $#ARGV is 0 whether there are zero or 1 arguments.

    That's not true. $#ARGV is -1 when there are no arguments.

    And by the way, I think -CS is unnecessary and even harmful. wmic probably outputs characters based on your local, so use open ':std', ':locale'; would be more appropriate.

      Hrmmm . . . I copied only the relevant code from my script, above:

      C:\chas_sandbox\columns-by-name> copy con ARGVtest.pl #!/usr/bin/perl -WCD use strict; use warnings; use Data::Dumper; $\ = $/; my $debug = 1; #array of fields to display my @processFields = ('Caption','ParentProcessId','ProcessId','CommandL +ine'); #ARGV processing my $searchfor = $ARGV[0] ? join(' ',@ARGV) : die("I need a process to +look for." ); ^Z 1 file(s) copied. C:\chas_sandbox\columns-by-name> notepad ARGVtest.pl

      and changed it a little (in notepad, above) and tested it:

      C:\chas_sandbox\columns-by-name> type ARGVtest.pl #!/usr/bin/perl -WCD use strict; use warnings; use Data::Dumper; $\ = $/; my $debug = 1; #array of fields to display my @processFields = ('Caption','ParentProcessId','ProcessId','CommandL +ine'); #ARGV processing my $searchfor = $#ARGV ? join(' ',@ARGV) : die("I need a process to lo +ok for."); # ^^^^^^ change here C:\chas_sandbox\columns-by-name> ARGVtest.pl I need a process to look for. at C:\chas_sandbox\columns-by-name\ARGVt +est.pl lin e 14. C:\chas_sandbox\columns-by-name> ARGVtest.pl chas I need a process to look for. at C:\chas_sandbox\columns-by-name\ARGVt +est.pl lin e 14.

      and added some debug (in notepad again) and tested again:

      C:\chas_sandbox\columns-by-name> type ARGVtest.pl #!/usr/bin/perl -WCD use strict; use warnings; use Data::Dumper; $\ = $/; my $debug = 1; #array of fields to display my @processFields = ('Caption','ParentProcessId','ProcessId','CommandL +ine'); #ARGV processing print Dumper(\@ARGV); print $#ARGV; my $searchfor = $#ARGV ? join(' ',@ARGV) : die("I need a process to lo +ok for."); C:\chas_sandbox\columns-by-name> ARGVtest.pl $VAR1 = [ '' ]; 0 I need a process to look for. at C:\chas_sandbox\columns-by-name\ARGVt +est.pl lin e 16. C:\chas_sandbox\columns-by-name> ARGVtest.pl chas $VAR1 = [ ' chas' ]; 0 I need a process to look for. at C:\chas_sandbox\columns-by-name\ARGVt +est.pl lin e 16.

      . . . and it sure looks like $#ARGV is 0 either way. I totally trust your experience, so I conclude that either my test is wrong or my conclusions are. Does the she-bang line arguments mess this up or something?


      #my sig used to say 'I humbly seek wisdom. '. Now it says:
      use strict;
      use warnings;
      I humbly seek wisdom.
        You have:
        my $searchfor = $#ARGV ? join(' ',@ARGV) : die("I need a process to lo +ok for.");
        • If there are no arg, $#ARGV is -1, $#ARGV is true, $search_for = join(' ', ()).
        • If there is one arg, $#ARGV is 0, $#ARGV is false, die("I need a process to look for.").
        • If there is two arg, $#ARGV is 1, $#ARGV is true, $search_for = join(' ', $ARGV[0], $ARGV[1]).
        • ...

        You want

        my $searchfor = $#ARGV >= 0 ? join(' ',@ARGV) : die("I need a process +to look for.");

        Or better yet:

        my $searchfor = @ARGV ? join(' ',@ARGV) : die("I need a process to loo +k for.");

        I don't understand why that's one one line. It doesn't save anything. I just adds complexity.

        @ARGV or die("I need a process to look for."); my $searchfor = join(' ',@ARGV);

      Thanks and ++ for the tip about :std or :locale. It's working in place as is, so I'll file this away for the next time I'm working on it. Again, it's a one-off script, but this is a good place to document the caveats for others who might be researching similar things.


      #my sig used to say 'I humbly seek wisdom. '. Now it says:
      use strict;
      use warnings;
      I humbly seek wisdom.

        Quick note, it's not :std or :locale. They work in conjunction.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://723220]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (3)
As of 2024-04-24 02:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found