Keith's profileKeith Hill's BlogPhotosBlogListsMore ![]() | Help |
|
November 24 Effective PowerShell Item 10: Understanding PowerShell Parsing ModesThe way PowerShell parses commands can be surprising especially to those that are used to shells with more simplistic parsing like CMD.EXE. Parsing in PowerShell is a bit different because PowerShell needs to work well as both an interactive command line shell and a scripting language. This need is driven by use cases such as:
Part and parcel with providing a powerful scripting language is to support more types than just the string type. In fact, PowerShell supports most .NET types including String, Int8, Int16, Int32, Decimal, Single, Double, Boolean, Array, ArrayList, StringBuilder among many, many other .NET types. That's very nice you say but what's this got to do with parsing modes? Think about this. How would you expect a language to represent a string literal? Well most folks would probably expect this representation: "Hello World" And in fact, that is recognized by PowerShell as a string e.g.:
And if you type a string at the prompt and hit the Enter key, PowerShell, being a very nice REPL environment, echoes the string back to the console as shown above. However what if I had to specify filenames using quotes as shown below?
That would immediately "feel" different than any other command line shell out there. Even worse, typing all those quotes would get really annoying, really fast. What to do, what to do? Well my guess is that the team, pretty early on, decided that they were going to need two different ways to parse. First they would need to parse like a traditional shell where strings (filenames, dir names, process names, etc) do not need to be quoted. Second they would need to be able to parse like a traditional language where strings are quoted and expressions feel like those you would find in a programming language. In PowerShell, the former is called Command parsing mode and the latter is called Expression parsing mode. It is important to understand which mode you are in and more importantly, how you can manipulate the parsing mode. Let's look at an example. Obviously we would prefer to type the following to delete files:
That's better. No bloody quotes required on the filenames. PowerShell treats these filenames as strings even without the quotes in command parsing mode. But what happens if my path has a space in it? You would naturally try:
And that works as you would expect. OK now what if I want to execute a program with a space in its path:
That didn't work because are far as PowerShell is concerned we gave it a string, so it just echoes it back to the screen. It did this because it parsed this line in expression mode. We need to tell PowerShell to parse the line in command mode. To do that we use the call operator '&' like so:
Tip: Help prevent repetitive stress injuries to your wrists and use tab (and shift+tab) completion for auto-completing the parts of a path. If the resulting path contains a space PowerShell will insert the call operator for you as well as surround the path with quotes. What's going on with this example is that PowerShell looks at the first non-whitespace character of a line to determine which mode to start parsing in. If it sees [_aA-zZ] or & or . or \ then PowerShell parses in Command mode. One exception to these rules happens when the line starts with a name that corresponds to a PowerShell language keyword like "if", "do", "while", etc. In this case, PowerShell uses expression parsing mode and expects you to provide the rest of the syntax associated with that keyword. The benefits of Command mode are:
So why do we need expression parsing mode? Well as I mentioned before it sure would be nice to be able to evaluate expressions like so:
It isn't a stretch to see how some shells might interpret this example as trying to invoke a command named '64-2'. So how does PowerShell determine if the line should be parsed in expression mode? If the line starts with a number [0-9] or one of these characters: @, $, (, ' or " then the line is evaluated in expression mode. The benefits of expression mode are:
One consequence of the rules for expression parsing mode is that if you want to execute an EXE or script whose name starts with a number you have to quote the name and use the call operator e.g.:
If you were to attempt to execute "64E1" without using the call operator, PowerShell can't tell if you want to interpret that as the number 64E1 (640) or execute the exe named 64E1.exe or the script named 64E1.ps1. It is up to you to make sure you have placed PowerShell in the correct parsing mode to get the behavior you want which in this case means putting PowerShell into command parsing mode by using the call operator. Note I have observed that if you specify the full command name e.g. 64E1.ps1 or 64E1.exe, it isn't necessary to quote the command. Now what if you want to mix and match parsing modes on the same line? Easy. Just use either a grouping expression (), a subexpression $() or an array subexpression @(). This will cause the parser to re-evaluate the parsing mode based on the first non-whitespace character inside the parens. Sidebar: What's the difference between grouping expressions (), subexpressions $() and array subexpressions @()? A grouping expression can contain just a single statement. A subexpression can contain multiple semicolon separated statements. The output of each statement contributes to the output of the subexpression. An array subexpression behaves just like a subexpression except that it guarantees that the output will be an array. The two cases where this makes a difference are 1) there is no output at all so the result will be an empy array and 2) the result is a scalar value so the result will be a single element array containg the scalar value. If the output is already an array then the use of an array subexpession will have no affect on the output i.e. array subexpressions do not wrap arrays inside of another array. In the following example I have embedded a command "Get-ChildItem C:\Windows" into a line that started out parsing in expression mode. When it encounters the grouping expression (get-childitem c:\windows), it begins parsing mode re-evaluation, finds the character 'g' and kicks into command mode parsing for the remainder of the text inside the grouping expression. Note that ".Length" is parsed using expression mode because it is outside the grouping expression, so PowerShell reverts back to the previous parsing mode. ".Length" instructs PowerShell to get the Length property of the object output by what was evaluated inside the grouping expression. In this case, it is an array of FileInfo and DirectoryInfo objects. The Length property tells us how many items are in that array.
We can do the opposite. That is, put expressions in lines that started out parsing in command mode. In the example below (admittedly lame) we use an expression to calculate the number of objects to select from the sequence of objects.
Using the ability to start new parsing modes, we can nest commands within commands. This a powerful feature and one I recommend mastering. In the example below PowerShell is happily parsing the command line in command mode when it encounters '@(' a.k.a. the start of an array subexpression. This causes PowerShell to re-evaluate the parsing mode but in this case it finds a nested command. One that grabs the new filename from the first line of the file to be renamed. I used the array subexpression syntax in this case because it guarantees that we will get an array of lines even if there is just one line. If you use a grouping expression instead and the file happens to contain only a single line then PowerShell will interpret the [0] to be "get me the first character in the string" which is "f" in the example below.
There is one final subtlety that I would like to point out and that is the difference between using the call operator (&) to invoke commands and "dotting" commands. Consider invoking a simple script that sets the variable $foo = 'PowerShell Rocks!. Let's execute this script using the call operator and observe the impact on the global session:
Note that using the call operator invokes the command in a child scope that gets thrown away when the command (script, function, etc) exits. That is, the script didn't impact the value of $foo in the global scope. Now let's try this again by dotting the script:
When dotting a script, the script executes in the current scope. As a result, the variable $foo in script.ps1 effectively becomes a reference to the global $foo when the script is dotted from the command line resulting in changing the global $foo variable's value. This shouldn't be too surprising since "dot sourcing", as it's also known, is common in other shells. Note that these rules also apply to function invocation. However for external EXEs it doesn't matter whether you dot source or use the call operator since EXEs execute in a separate process and can't impact the current scope. Here's a handy reference to help you remember the rules for how PowerShell determines the parsing mode.
Once you learn the subtleties of these two parsing modes you will be able to quickly get past those initial surprises like how you execute EXEs at paths containing spaces to putting these parsing modes work for you. November 13 Updated My Zune30 to Version 2.2Wow! How cool is it that Microsoft didn't leave the early adopters of the Zune out in the cold WRT the features available in the new Zune models. I just updated my PC side Zune software and my device and yes finally, I can easily sync up Media Center recorded TV onto my Zune and play it back. I have to say that I was more than a little disappointed that this feature wasn't available when the Zune first shipped but hey, better late than never. I also really dig the support they have added for syncing podcasts. The "Social" aspect on the Zune software service is interesting. Folks can view what you have been listening to and what your favorites songs are. You can out my Zune Card here. I would like to see them extend this concept to what podcasts folks are listening to. FYI some folks upgrading to the new PC side Zune software have been running into problems with mixed up album art. I ran into this issue and found this post in the Zune forums which fixed the issue for me. November 08 PowerShell v2 CTP Help FileThe PowerShell team has made a CHM help file available for v2. This should be a useful learning tool. You can download it from here. Check out the help topic on keyboard shortcuts. Nice but they are missing one handy shortcut:
November 06 PowerShell v2 CTP is OutThe PowerShell has released the v2 CTP out into the wild. Remember what I said earlier about evaluating this on either a throw away test PC image or a Virtual PC image. You can get the bits from the link in this PowerShell Team blog post about v2 CTP. There is a lot to like in this release like support for remote management and background jobs (oh I like these). But there are some small things that are also really nice e.g.: PS> 1TB PS> 1PB PS> "a","b","c" -join ';' PS> $env:path -split ';' # Splatting operator to map array items to individual parameters And for one of my favorite new enhancements - Select-String -AllMatches: PS2> $pattern = 'GET /ongoing/When/\d{3}x/(\d{4}/\d{2}/\d{2}/[^ .]+)' There is also a -Context parameter on Select-String that will show you N number of strings (lines) before and after the selected string. And for the "grep -v" lovers there is the -NotMatch parameter. Nice. November 03 PowerShell v2 CTP Coming Next WeekSo our first peek at what is coming in v2 of PowerShell is nearly here. There are a couple of provisos you should be aware of. This is a community technology "preview". It isn't even of beta quality. Second, it does not install side-by-side with V1. You have to uninstall V1 first if you decide to install this CTP. My advice for any beta software and *ESPECIALLY* CTP software is to *NEVER* install this on your main machine or a production machine. A virtual machine is the way to go on this type of pre-release software. Personally I use Virtual PC and I plan to run v2 CTP on the Orcas Beta 2 Virtual PC image. Microsoft Virtual PC 2007 is FREE! There really isn't much of an excuse not to try it out at least. You can get started with the free download from here. Anybody that wants to also try out VS 2008 Beta 2, can grab the VPC images from here. Please read more about the v2 PowerShell CTP on the PowerShell blog. They have a good post on setting expectations for this CTP. PowerShell Community Extensions Impact: It appears that PSCX will install and run on this CTP. It seems that v2 of PowerShell, in this CTP at least, is compatible with snapins built against v1. There is at least one problem that we have identified and it has to do with a type data conflict. If you install PSCX 1.1.1 on the v2 CTP, please edit this file $PscxHome\TypeData\FileSystem.ps1xml after you have installed PSCX and remove the following section: <Type> Save the file and re-add the PSCX snapin (or start a new PoSh session if you're using the PSCX profile) and you shouldn't get any startup errors. |
|
|