Tags

,


I was writing a function earlier to uninstall GearASPI which iTunes always insists on putting on my machine.  The GEAR website has a list of manual steps but no uninstaller.  One of the steps which will be familiar to anyone who has had to fight with disappearing CD drives in the past is to edit the UpperFilters multi-string registry value.  This used to be a relatively common problem caused by various pieces of CD-writing software that didn’t play nicely together.  Advice was often to just delete the UpperFilters and LowerFilters values to restore CD drive functionality but that would affect all software that had altered those keys and not just the one that was playing badly.  The GEAR site wisely says to edit the values if they’re present rather than deleting them.  So the question becomes, how to safely edit a registry Multi-String value (REG_MULTI_SZ) from PowerShell.

The Registry PSProvider has always confused me with its keys being Items and its values being ItemProperties.  Here was what I started with:

$regKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e965-e325-11ce-bfc1-08002be10318}'
$upperFilters = Get-ItemProperty -Path $regKey -Name 'UpperFilters'

Viewing the returned variable gives us the following:

UpperFilters : {GearASPIwdm}
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4d36e965-e325-11ce-bfc1-08002be10318}
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class
PSChildName : {4d36e965-e325-11ce-bfc1-08002be10318}
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry

That’s not really very helpful so I tried two ways of getting at the actual value:

Get-ItemProperty -Path $regKey -Name 'UpperFilters' | Select-Object -ExpandProperty 'UpperFilters'
(Get-ItemProperty -Path $regKey -Name 'UpperFilters').UpperFilters

My UpperFilters value only contained a single string.  In this case, the first technique returned a [string] whereas the second one returned the desired [string[]].

If the registry value contains no strings, the first technique errors because null is returned and the second technique gives [string[]] again as desired.

I always want [string[]] so technique two looks safe.  Next comes the problem of how to remove a given value from a string array.

Having looked at the methods of the returned variable, I tried .Remove():

$newUpperFilters = $upperFilters.Remove("gearaspiwdm")

That was less than useful:

Exception calling "Remove" with "1" argument(s): "Collection was of a fixed size."

One method I tried yesterday to get around this was casting the original Get-ItemProperty to an [ArrayList] and using its .Remove() method:

[System.Collections.ArrayList]$upperFilters = (Get-ItemProperty -Path $regKey -Name 'UpperFilters').UpperFilters
$newUpperFilters = $upperFilters.Remove('gearaspiwdm')

Oddly, that’s working for me today but yesterday I was finding that the .Remove() was being case-sensitive!  I therefore went on to other things.  This is what I ended up with:

$newUpperFilters = $upperFilters | Where-Object { $_ -ne 'gearaspiwdm' }

That successfully removed the desired string but if there were no other strings left, I ended up with a null to deal with.  I wanted to always get a [string[]] back.

I tried various ways of casting the result:

[string[]]$newUpperFilters = $upperFilters | Where-Object { $_ -ne 'gearaspiwdm' }
[Array]$newUpperFilters = $upperFilters | Where-Object { $_ -ne 'gearaspiwdm' }
$newUpperFilters = [string[]]($upperFilters | Where-Object { $_ -ne 'gearaspiwdm' })
$newUpperFilters = [Array]($upperFilters | Where-Object { $_ -ne 'gearaspiwdm' })
$newUpperFilters = @($upperFilters | Where-Object { $_ -ne 'gearaspiwdm' })

Out of all of those, only the last entry always returned a collection even when removal of the matching string left nothing.  The others return null but the last one gives back an [object[]].  Though not actually necessary, that can be converted into a string array with a simple cast:

[string[]]$newUpperFilters = @($upperFilters | Where-Object { $_ -ne 'gearaspiwdm' })

Whilst writing this up today, I realised that there was a much easier method that would always return a [string[]]:

[string[]]$newUpperFilters = $upperFilters -notmatch 'gearaspiwdm'

failfish

Note that my final function included Test-Path on registry keys and a Remove-ItemProperty if the UpperFilters ended up empty (based on the .Count property of $newUpperFilters).

Now I’m off to re-consult Bruce Payette’s bible and see what he says about Type conversion.  I think I understand the difference between all those casts I tried above…


Update

Having now done some reading, one thing that was seemingly clarified for me is why @() behaves differently to [Array] in ensuring I always got a collection back.

The clue was Bruce’s name for @( … ) as an “Array Subexpression”.  I guess its physical similarity to the subexpression $( … ) should have been a clue.  Apparently @( … ) is equivalent to [object[]] $( … )

Except when I tested this, I didn’t see the same behaviour!

@(([string[]]'Test') | where {$_ -ne 'Test'}).GetType().Name

That returns “Object[]”.

[object[]]$(([string[]]'Test') | where {$_ -ne 'Test'}).GetType().Name

That returns an error due to null being returned:

You cannot call a method on a null-valued expression.

I remain confused!!

Advertisements