Case sensitive ValidateSet.

OK, I know it may be useless in 99% of cases, but lets say you fall in the 1%. You not only care about the string itself, but also about case used. E.g. you don’t want user to pollute produced objects with scary thing like: lApToP. What can you do?

So far I was sure only combination of ValidateSet and ValidatePattern could make it possible. And due to default regex behaviour in PowerShell – it would be complicated even there (need to explicitly disable IgnoreCase option also in regex pattern):

function Get-Foo {
param (
    [ValidateSet('laptop','desktop')]
    [ValidatePattern('(?-i:^[a-z]+$)')]
    [string]$Type
)
    $Type
}

That would work, obviously. But recently I was reading docs for other Validate* class on MSDN, and out of curiosity I selected ValidateSet. And what I found? There is named option in it, IgnoreCase, that defaults to true. At first I could not figure out how to use it and set to false. Than I compared it with other attribute declarations and syntax for named parameters for those within PowerShell, like CmdletBinding (plenty of named parameters there). And because I use those in PowerShell a lot it was easy to translate the one in ValidateSet:

function Get-Bar {
param (
    [ValidateSet('laptop','desktop',IgnoreCase = $false)]
    [string]$Type
)
    $Type
}

It was not documented in PowerShell itself (or: I was not able to spot it in the help anywhere), so I thought it might be worth sharing. Puszczam oczko Just in case someone need it. I know I did and eventually added enum class to support this…

PowerShell vNext: AST.

What is that?

There is one addition in PowerShell vNext that I look really forward to. In v2 we have parser available. It makes work with PowerShell code easier. But PowerShell team haven’t stopped there. AST (Abstract Syntax Tree) is new concept (at least – to me) and it makes parsing code more interesting: instead of stream of tokens (which is still there with AST and is also more accurate than v2 precedents) we get tree of syntax: from Script to single Statement. Browsing thru that tree may be very hard at first. I tried to use view that seems natural for that kind of complex structures: Format-Custom. The problem that I went into was the fact, that each AST contains “Extent” and “Parent” property. And those properties keep whole code which makes Format-Custom lengthy and impractical. So my first step was to create .ps1xml files to make format and object themselves bit different. I hidden two properties I think are not very useful. You can still access them using psbase on each object – and that exactly the information you get if you try to access them directly. Next step I took was to create simple function that would wrap .NET call to System.Management.Automation.Language.Parser static methods (ParseInput, ParseFile) and make use of them more natural and PowerShell-ish. Then I packed it all in the module, so that I could paste it somewhere and let others use it and complain about things I did wrong (like using global scope to store tokens/ errors).

How would I use it?

Parser is the way to look closer at your code. If you want to extend editors of your choice (ISE, PowerGUI Script Editor, or any other you like and use) than having ability to read code as PowerShell does makes some operations more error-prone. It was the case with tokens in v2 – but it’s more the case with addition of AST in v3. Now you know exactly where you are, and you know what to expect. What language elements are justified in current AST, and which are wrong. When I was playing with new stuff I noticed, that it’s not replacement (as far as I can tell) for “old” technique. It’s just extension to it. But I’m not sure if that’s actually the case – it’s probable that I’m missing something obvious. For example: new tokens do not contain any info about absolute position in a script. On the other hand – being able to read nested tokens makes some operations easier (e.g. expanding aliases in sub-expression within a string). Example:

$Script = @'
"$($Foo.Count)"
'@             

$AST = Get-AST -InputScript $Script -TokensList TK            

Write-Host New tokens - nested tokens
$TK | where { $_.NestedTokens } |
    Select -ExpandProperty NestedTokens
Write-Host Old tokens - no info of nested tokens here
[Management.Automation.PSParser]::Tokenize($Script, [ref]$null)

Where is this module?

For now I’ve pasted it into TechNet script gallery. I would paste it into poshcode too, but because .psm1 file is just 1/3 of whole story I decided it makes no sense (yet). If you have some other ideas on what would be nice to have there – go ahead and change it, or let me know and I will try to do it. Uśmiech Would love to see others picking up this neat new concept. In my opinion in makes working with PowerShell code more natural – after all it is, in fact, more tree-like construct rather than flat stream of tokens. Having ability to see this tree structure up front may mean a lot to people who, like me, love PowerShell not only for it’s usability but also for the way it is built. Uśmiech

Select groups of code in ISE.

New(er) ISE comes with lots of cool features. One of them is group-matching in editor. This is more than expected from modern editor. But wouldn’t that be handy to have possibility to easily select whole group? If you put cursor at the border of group – matching charter will be highlighted to simplify job. But for me – that’s too much work. How about simple function that would select whole group for me?

function Select-Group {
    if ($Editor = $psISE.CurrentFile.Editor) {
        if ($Editor.CanGoToMatch) {
            $startLine, $startColumn =
                $Editor.CaretLine, $Editor.CaretColumn
            $Editor.GoToMatch()
            $endLine, $endColumn =
                $Editor.CaretLine, $Editor.CaretColumn
            $Editor.GoToMatch()
            # We can't be sure caret was at beg of group...
            if ($startLine -gt $endLine) {
                $Editor.Select($endLine, $endColumn,
                    $startLine, $startColumn)
            } else {
                $Editor.Select($startLine, $startColumn,
                    $endLine, $endColumn)
            }
        }
    }
}

Now all you have to do is assign hotkey to it and you can select groups at will. Enjoy! Puszczam oczko

Update: quick tip needs quick update – forgot that sometimes (often?) group is all on one line, and column has to be checked in such scenario to avoid exception, so if clause that decides on $Editor.Select syntax should be a bit different:

(($startLine -gt $endLine) -or
    (($startLine -eq $endLine) -and ($startColumn -gt $endColumn)))

Hope now I haven’t missed any other important exception. Uśmiech

CTP1 to CTP2 gotcha… :)

Ok, CTP2 is out. This is a great news, and a lot of things to play. Newer ISE, finally fixed “killer TAB” in console, Update-Help that picks up data from network and more.

There is one thing you need to be aware of: CTP1 and CTP2 both installed at the same time is a really bad idea. Trust me, you don’t want to see it (any command results in pages and pages of errors, tab does not work, highlighting syntax in ISE is total disaster).

So first thing: don’t do it. Uninstall CTP1 first. Documentation is clear about that and I know already why. Puszczam oczko

But let’s say you did that mistake already. Information “you shouldn’t!” is not really helpful now, right? I went that path so I decided I would share. It’s nothing spectacular, all I did was:

  • removed CTP2 (reboot)
  • installed CTP1 (reboot)
  • removed CTP1 (ignore the fact, that it may show up as CTP2 anyway – and yes, reboot)
  • install CTP2 on clean v2 and enjoy the show! Uśmiech

I would also double-check how it goes after each step (make sure CTP1 works, make sure clean v2 actually is clean). Hope it helps some other people who prefer to use things firsts and read instructions later… Puszczam oczko

Temporary file with given extension.

Creating temporary file in PowerShell is pretty simple – because you can use .NET call directly it’s a matter of:

[IO.Path]::GetTempFileName()            

The problem you may walk into is the rare case when your file has to have specific extension to work properly. I usually have this issue when I try to use temporary file to create formatting/ type information at runtime. If you leave .tmp extension, you can not use it for Update-*Data:

Cannot read file "C:\Users\bielawb\AppData\Local\Temp\tmp3069.tmp"  because it does not have the extension "ps1xml".

Obviously – you need to rename the file. Nothing really tricky here. But with PowerShell’s pipeline model you can do all that in one go instead of juggling variables:

[IO.Path]::GetTempFileName() |
    Rename-Item -NewName { $_ -replace 'tmp$', 'ps1xml' }PassThru

You will get FileInfo object back, and it will have correct extension for *Data files. All you need to do is put your XML in it and change your environment to suite your needs. Puszczam oczko

Quick Tip: cleaning code copied from blogs.

I’ve decided that from time to time I should use this blog to write down some tiny things I came up with while working/ playing with PowerShell. My memory is leaking all the time, and once I write something down – I will be able to come back to it when needed. Puszczam oczko

Today I had need to copy some code from blog. Having PowerShell code on the blog is pretty neat, it’s usually nicer when you add line numbers. Line numbers are handy, but if you do not use plugin that allows others to take code without them – they get extra step of removing prefixes from copied “stuff”. Today I decided that it would be wiser to have some simple function to do that for me, instead of writing some one-liner each time:

function Remove-LinePrefix {
param (
    [Alias('PrefixPattern', 'PP')]
    [string]$Pattern
)            

if ($Text = $psISE.CurrentFile.Editor.Text) {
    $Text = $Text.Split("`n") | ForEach-Object {
        $_ -replace $Pattern
    }
    $psISE.CurrentFile.Editor.Text = $Text
}
}             

With this function defined I could copy code from blog directly to new ISE window and with single line (and proper regex pattern that would match only the prefix) clean it up:

Remove-LinePrefix -Pattern '^\s+\d+:'

Done! Uśmiech

Begin, process, end…

Few weeks ago I’ve discovered very strange bug in the way ‘process’ works when used in wrong context. Long story short: someone wrote function with process block, but without begin block. It’s usually OK, but he tried to put some code between param and process block and that gave him error, that is misleading at best:

function Test-BadProcess {
param ()            

# begin {
$SomeVariable = 'Initial Value'
# }            

process {
    "Will barf on you! Get-Process does not take scripts! ARGH!"
}
}

So how it looks like when you run it?

BadProcess

Oops? Get-Process? When did I used that command? It appears that when you use process in other context than it was designed to, it will automagically add ‘Get-‘ verb and surprise you. What is even more surprising – it will do the same with begin and end. But first it has to have something to run so…:

function Get-Begin {
    "And now for something completely different!"
}            

function Get-End {
    "This is the end... My only friend, the end..."
}            

$null;begin
$null;end

But that’s not all, folks. Unless you use something that PowerShell would not like (e.g. keywords that do not change into commands when context changes) or known already as a command (alias, application) you can just type Noun, and Get- will be added for you. How I’ve found out? Trace-Command told me… Puszczam oczko

Get-Foo

The only question that remains open: why process/ begin/ end used in such context follow this rule? And few other keywords too (more on that later).

BTW: in case you want to fix it yourself: my suggestion is to define three functions in your profile:

foreach ($key in 'begin process end'.Split()) {
    $Function = @{
        Path = "function:\$key"
        Value = [scriptblock]::Create(
            "throw 'Keyword ''$key'' used in wrong context!'"
        )
        Force = $true
    }
    New-Item @Function | Out-Null
}            

Any of them should give you errors that are more accurate than what you get by default… You may want to fix also other surprising keywords that change into commands in context, where you might not expect it:

'Begin', 'Break', 'Catch', 'Continue',
'Data', 'Do', 'Dynamicparam', 'Else',
'Elseif', 'End', 'Exit', 'Filter',
'Finally', 'For', 'Foreach', 'From',
'Function', 'If', 'In', 'Param',
'Process', 'Return', 'Switch', 'Throw',
'Trap', 'Try', 'Until', 'While' | ForEach-Object {
    "$_";"`$null;$_";"} $_"
} | ForEach-Object {
    [Management.Automation.PSParser]::Tokenize($_, [ref]$null) |
    Add-Member -MemberType NoteProperty -Name Line -Value $_ -PassThru
} | Where-Object { $_.Type -in 'Command', 'Keyword'} |
    Format-Table Line, Type -AutoSize -GroupBy Content

Here you can see all keywords and how they switch their keyword-ness on and off.

I’m only sad I forgot to mention this to PowerShell Team during Deep Dive and ask why is that so. Smutek Note to self: next time prepare a list of questions before you go to conference like this one, amount of PowerShell goodness my overwhelm you and in the end – you forget what was that you wanted to ask. Puszczam oczko