Friday, June 24, 2011

Powershell Scripting Part 1- Stupid little things nobody ever bothers to tell you

In my experience, there are two types of tutorials for everything: the most fundamentally basic and the most terrifyingly advanced.  When I was first learning powershell, I found a distinct lack of intermediate level tutorials.  So here's a blog post dedicated to telling you everything you ought to know but nobody ever bothers to tell you about powershell.

Variable Expansion

You probably know how to create variables in powershell, but in case you don't:

>>$PROCS=get-wmiobject -namespace root\cimv2 -query "Select * from win32_process"

$PROCS now contains an array of processes, and executing

>>$PROCS

displays all the information about every process on the system.  But what if you want just the CommandLine value for each of the processes?  Well, of course you could instantiate a new variable like

>>$COMMANDLINES=get-wmiobject -namespace root\cimv2 -query "Select CommandLine from win32_process"

but then you lose some informtion; you can't get the process id for the process with the command line you're looking for, for example.  Ok, so lets try to echo the command line and process ID properties:

>>foreach($PROC in $PROCS){echo $PROC.ProcessId : $PROC.CommandLine;}

Unfortunately, this echos each part on a seperate line.  The solution is to put quotes around the blocks you want to output on their own lines:

>>foreach($PROC in $PROCS){echo "$PROC.ProcessId : $PROC.CommandLine";}

But something unexpected happens!  instead of "804 : C:\Windows\System32\mspaint.exe", we end up with "<crasy looking string>.ProcessId : <equally crazy looking string>.CommandLine".  So what happened?  Well, variable expansion happened.

By placing the variables in quotes, we caused $PROC to get expaned into its full string value.  It turns out that when you expand a wmi object variable, it resolves to the __Path property of that variable.  So in essence, we just did the same thing as typing "<proc path>.ProcessId : <proc path>.CommandLine".  No surprise that these values don't get replaced by the values we actually wanted to put there, is it? 

So how do we fix it?  Well, when a quoted value has the '$' symbol in it, its a sign to powershell that you want to expand whatever the next token is.  So, we just have to make the next token "<proc path>.ProcessId" instead of $PROC.  It would look like this:

>>foreach($PROC in $PROCS){echo "$($PROC.ProcessId) : $($PROC.CommandLine)";}

In this case, powershell sees the '$', and tries to expand the next token. But it sees the (), and realizes that it has to expand whatever is inside the parenthesis first. So it expands $PROC.ProcessId into <proc path>.ProcessId, same as before, and then it goes ahead and expands <proc path>.ProcessId into the actual value we wanted to pass in the first place.

You can do as many of these nestings as you want. But just remember, any time you're trying to use a variable's property inside quotes, you need to do this- including inside query statements.  For example, if you want to refresh the properties of your win32_process object (remember, objects are a static snapshot of the system; if the process exits after you grab the wmi object, it won't be reflected in the object until you grab it again), you can do the following:

>>$PROC=get-wmiobject -namespace "root\cimv2" -query "Select * from win32_process where ProcessId=$($PROC.ProcessId))"

No comments:

Post a Comment