Modules that modify your environment.

Modules usually just keep few functions together and so you are perfectly fine with removing those functions when module is removed. But that is not always the case. Let say you want to modify prompt, or overwrite it for the time when module is loaded:

# Simple module to modify my prompt            
function prompt {            
    "$pwd ]> "            
}

It is fine as long module is loaded. Now try to unload it. You are left without prompt function. My first guess was that I should re-create this function in special scriptblock that is invoked when module is removed:

# Simple module to modify my prompt            
# But first let's create backup copy...            
$oldPrompt = Get-Content function:\prompt            
            
function prompt {            
    "$pwd ]> "            
}            
            
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {            
    Set-Content function:\prompt -Value $OldPrompt            
}            

But unfortunately it won’t work either. Why? Try to Remove-Module –Verbose and add some Write-Host to OnRemove scriptblock, you will notice that this method is invoked before imported functions are being removed. Funny, that when I initially thought about that I did some other thing, simply modified contents of prompt function:

# Simple module to modify my prompt            
# But first let's create backup copy...            
$oldPrompt = Get-Content function:\prompt -ErrorAction SilentlyContinue            
$addedPrompt = @'
# This is only a comment but could be something useful too.

'@            
            
Set-Content function:\prompt -Force $($AddedPrompt + $oldPrompt)            
            
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {            
    Set-Content function:\prompt -Value $OldPrompt            
}            

I was surprised to see, that if I don’t do anything with prompt function in OnRemove it will get removed together with module, and if I do something – it remains untouched (you can see it when –Verbose view is on). It was done like that in my CD module that uses prompt to save each PSContainer that you walk into and as far as I can tell – works fine. Now I wanted to overwrite psEdit function when module to work with PowerShell SE is loaded. But to have option to re-create it I had to again use Set-Content. If there is other option – I had no luck finding one. So now instead of normal function definition I have long herestring. Yak. 😦

Update after Jason’s comment.

So, eventually I got prompt reply from a person who I should probably asked first – one of authors of brilliant extension for PowerShell Tab completion (I have it in my PowerShell.exe profile to make working with it less horrible 😉 ). PowerTab is overwriting TabExpansion function, and once module in removed (I would do that only for working with ActiveDirectory provider probably) it is restored to it’s original version. This solution requires you to define function in global scope rather than default one and you have to restore it back in a way I just did:

# Simple module to modify my prompt            
# But first let's create backup copy...            
$oldPrompt = Get-Content function:\prompt -ErrorAction SilentlyContinue            
            
function global:prompt {            
    "$pwd ]:> "            
}            
            
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {            
    Set-Content function:\prompt -Value $OldPrompt            
}            

So if you want to overwrite function than that is probably the best way to do that. Result is similar to the one I got with my workaround: no action on function if it’s modified in OnRemove, removed if no action was taken. And my psEdit is looks like function again. 😀 Thanks, Jason!

Advertisement

HashTables to build an object.

You probably know already v2 feature that allows you to pass hashtable to New-Object cmdlet and have key-value pairs changed into property names – values. I love this feature so much that I usually try to write code using it as much as I can. E.g. description of windows.forms.button may look like this:

$button = New-Object Windows.Forms.Button            
$button.Text = 'OK'            
$size = New-Object Drawing.Size            
$size.Height = 200            
$size.Width = 20            
$button.size = $size

And that works fine, but I avoid this type of syntax. My version would look like that:

$Mybutton = New-Object Windows.Forms.Button -Property @{            
    Text = 'OK'            
    Size = New-Object Drawing.Size -Property @{            
        Height = 200            
        Width = 20 }            
    }

Beauty is in an eye of beholder so I won’t convince you mine is better. But I guess it is clear now, that I simply love this v2 way of adding/ defining properties of an object. So when I saw few times question about recursive object creation using hashtables I decided I have to give it a try. So here is my version that creates multi-level PSObject:

function New-ObjectRecursive {            
    param(            
        $Property            
        )            
    if ($Property -is [hashtable]) {             
        $PropertyHash = @{}            
        foreach ($key in $Property.keys) {            
            $PropertyHash.Add($key, $(New-ObjectRecursive $Property[$key]))            
        }            
        New-Object PSObject -Property $PropertyHash            
    } else {            
        $Property            
    }            
}

Now – what I would like to do is somehow ‘inject’ object type. Maybe I will find a way to have something like:

$MyButton = New-ObjectRecurse @{ Type = 'Windows.Forms.Button';            
    Text = 'OK'            
    Size = @{Type = 'Drawing.Size'; Width = 200; Height = 20 }            
    Location = @{ Type = 'Drawing.Point'; X = 10; Y = 10 }            
    }

For now I can only create stuff like that:

$obj = New-ObjectRecursive -Property @{ Alfa = 1; Beta = @{ One = 1; Two = 2; Three = @{ Deep = 'Foo'; Very = 'Sweet' } } }            
$obj.Beta.Three.Deep
Foo

But somebody asked for it, so maybe he has some use case for that… I hope. 😉

So I did it! 🙂

EDIT: soon after I’ve posted I tried to get to the point where I wanted to be. So now you can either give your hashtable a ‘Type’ key to define a type, or skip it and have PSObject (default type):

function New-ObjectRecursive {            
    param(            
        $Property            
        )            
    if ($Property -is [hashtable]) {            
        if ($Type = $Property.Type) {            
            $Property.Remove('Type')            
        } else {            
            $Type = 'PSObject'            
        }            
        $PropertyHash = @{}            
        foreach ($key in $Property.keys) {            
            $PropertyHash.Add($key, $(New-ObjectRecursive $Property[$key]))            
        }            
        New-Object $Type -Property $PropertyHash            
    } else {            
        $Property            
    }            
}            

Enjoy! 😀

Alias property and ps1xml to rescue!

I work very often with WMI. What I do not like about it is the fact that I need to do something with that ugly __SERVER automatic property to make output nice. Let’s say I want to get serials from bunch of computers:

$Computers = 'WAR000260 PL-WAR-AP001 PL-WAR-VFP001'.Split()
Get-WmiObject -ComputerName $Computers -Class Win32_BIOS | select SerialNumber, ComputerName

That syntax won’t work. And yes, there is from time to time some property that returns remote computer name. But it’s not consistent in any way. I usually just use some hashtable tricks to change __SERVER into something more friendly. But that’s not the only way. For me easiest way would be to simply have consistent property that would give me what I need – ComputerName. Logic behind it is very simple. Since extending types in PowerShell is easy (once you figure out XML structure), why not add it at this level? XML for that is just few lines:

 

<?xml version=”1.0″ encoding=”utf-8″ ?>

<Types>

<Type>

<Name>System.Management.ManagementObject</Name>

<Members>

<AliasProperty>

<Name>ComputerName</Name>

<ReferencedMemberName>__SERVER</ReferencedMemberName>

</AliasProperty>

</Members>

</Type>
</Types>

All you need to do is save it as .ps1xml file, and load it – either before or after default (MS) type extensions. I’m loading it before:

Update-TypeData -PrependPath (Join-Path (Split-Path $profile) WMI.types.ps1xml)

Once it is done _all_ classes from WMI will get new AliasProperty ComputerName equal to __SERVER. And that (if you haven’t filtered it out) should always be there:

Get-WmiObject -Class Win32_BIOS | Get-Member ComputerName

   TypeName: System.Management.ManagementObject#root\cimv2\Win32_BIOS

Name         MemberType    Definition
----         ----------    ----------
ComputerName AliasProperty ComputerName = __SERVER

No more hashtables, no more looking at ugly __SERVER. I only wonder why PowerShell team haven’t done it. Probably I just broke something important.. But it looks nice, right? 🙂

Bug or ‘feature’? ;)

I’m in the middle of MS PowerShell training and today my colleague, Marcin, went into strange issue when working in WMI. Basically when he got two commands with select at the end of each he was getting only properties from first object collection. Second looked empty, or rather full of blanks. After we tried several things we discovered, that this is not WMI, nor second select that is causing this strange behaviour. Try yourself:

Get-Process | select Id            
Get-Service | select Name

Result? List of Ids and than many empty lines. This won’t happen if you try to launch each command separately. It won’t happen if you redirect first command to Out-Default (something I was sure was ALWAYS done automagically). It get’s even worse if you try to format output of second command, try:

Get-Process | select Id -First 2            
Get-Service | Format-List

You will get error similar to one you get when you e.g. try to sort after you format:

The object of type "Microsoft.PowerShell.Commands.Internal.Format.FormatStartData" is not valid or not in the correct sequence. 
This is likely caused by a user-specified "format-list" command which is conflicting with the default formatting.

I wonder if you walked into the same issue? Maybe it is already logged on connect? The only reasonable workaround I found so far is intentional redirection to out-default. What is most surprising to me is that I have never noticed that behaviour. And I select things quite often… Maybe that’s because I usually consume them and the real problem is selects left alone in the pipeline… 😉

PS: It was tested on W2K8R2 and Win7.