Rename Hashtable Key

talkbubbleI use hashtables quite a bit. Often generating hashtables on the fly from other sources. But sometimes the hashtable keys that come from these external sources don’t align with what I intend to do with the hashtable. For example, one of the nifty things you can do with hashtables is splat them against a cmdlet or advanced function. Each hashtable key will bind to its corresponding parameter name. This makes it very easy to run a command and pass a bunch of parameters all at once. Here’s an example of splatting in action.

PS C:\> $hash = @{Computername="Serenity";Class="AntivirusProduct";Namespace="root\securitycenter2"}
PS C:\> get-wmiobject @haSH

__GENUS                  : 2
__CLASS                  : AntiVirusProduct
__SUPERCLASS             :
__DYNASTY                : AntiVirusProduct
__RELPATH                : AntiVirusProduct.instanceGuid="{D68DDC3A-831F-4fae-9E44-DA132C1ACF46}"
__PROPERTY_COUNT         : 6
__DERIVATION             : {}
__SERVER                 : SERENITY
__NAMESPACE              : ROOT\securitycenter2
__PATH                   : \\SERENITY\ROOT\securitycenter2:AntiVirusProduct.instanceGuid="{D68DDC3A
                           -831F-4fae-9E44-DA132C1ACF46}"
displayName              : Windows Defender
instanceGuid             : {D68DDC3A-831F-4fae-9E44-DA132C1ACF46}
pathToSignedProductExe   : %ProgramFiles%\Windows Defender\MSASCui.exe
pathToSignedReportingExe : %ProgramFiles%\Windows Defender\MsMpeng.exe
productState             : 397568
timestamp                : Fri, 04 Jan 2013 17:09:42 GMT

This works because all of the hashtable keys line up with parameter names. If the names don’t match they are ignore. So sometimes I need to rename the hashtable key. This isn’t too difficult conceptually.

1. Create a new entry with the new key name and the old value
2. Remove the existing key.

But, I decided to turn this into an advanced function, probably with more bells and whistles than I really need. But maybe you will learn something new from my efforts.

Function Rename-HashTable {
#comment based help is here

[cmdletbinding(SupportsShouldProcess=$True)]

Param(
[parameter(position=0,Mandatory=$True,
HelpMessage="Enter the name of your hash table variable without the `$")]
[ValidateNotNullorEmpty()]
[string]$Name,
[parameter(position=1,Mandatory=$True,
HelpMessage="Enter the existing key name you want to rename")]
[ValidateNotNullorEmpty()]
[string]$Key,
[parameter(position=2,Mandatory=$True,
HelpMessage="Enter the NEW key name")]
[ValidateNotNullorEmpty()]
[string]$NewKey,
[switch]$Passthru,
[ValidateSet("Global","Local","Script","Private",0,1,2,3)]
[ValidateNotNullOrEmpty()]
[string]$Scope="Global"
)

#validate Key and NewKey are not the same
if ($key -eq $NewKey) {
 Write-Warning "The values you specified for -Key and -NewKey appear to be the same. Names are NOT case-sensitive"
 Return
}

Try {
    #validate variable is a hash table
    write-verbose (gv -Scope $scope | out-string)
    Write-Verbose "Validating $name as a hashtable in $Scope scope."
    #get the variable
    $var = Get-Variable -Name $name -Scope $Scope -ErrorAction Stop
       
    if ( $var.Value -is [hashtable]) {
        #create a temporary copy
        Write-Verbose "Cloning a temporary hashtable"
        <#
            Use the clone method to create a separate copy.
            If you just assign the value to $temphash, the
            two hash tables are linked in memory so changes
            to $tempHash are also applied to the original
            object.
        #>

        $tempHash = $var.Value.Clone()
        #validate key exists
        Write-Verbose "Validating key $key"
        if ($tempHash.Contains($key)) {
            #create a key with the new name using the value from the old key
            Write-Verbose "Adding new key $newKey to the temporary hashtable"
            $tempHash.Add($NewKey,$tempHash.$Key)
            #remove the old key
            Write-Verbose "Removing $key"
            $tempHash.Remove($Key)
            #write the new value to the variable
            Write-Verbose "Writing the new hashtable"
            Write-Verbose ($tempHash | out-string)
            Set-Variable -Name $Name -Value $tempHash -Scope $Scope -Force -PassThru:$Passthru |
            Select-Object -ExpandProperty Value
        }
        else {
            Write-Warning "Can't find a key called $Key in `$$Name"
        }
    }
    else {
        Write-Warning "The variable $name does not appear to be a hash table."
    }
} #Try

Catch {
    Write-Warning "Failed to find a variable with a name of $Name."
}

Write-Verbose "Rename complete"
} #end Rename-Hashtable

The function assumes you have saved the hashtable to a variable. Pass the variable name as a parameter, without the $ character. The function uses Get-Variable to retrieve the variable. Remember that variables are scope specific so I need to specify the scope to search. I’m defaulting to the global scope. Otherwise, when the function runs, it is in its own scope which doesn’t include the hashtable variable. Now, I could have simply attempted to read the variable, trusting PowerShell to read up the scope until it found it, but the best practice is to avoid referencing out of scope items.

Once the variable is found, its value is the actual hashtable which I verify as a [hashtable] object.

if ( $var.Value -is [hashtable]) {

From here I create a cloned copy of the original variable.

$tempHash = $var.Value.Clone()

By the way, you’ll notice my functions supports ShouldProcess. This means I can use -WhatIf and it will be passed to any cmdlets that also support it. At the end of my command I use Set-Variable which supports ShouldProcess so if I run my function with -WhatIf, Set-Variable will also use -Whatif. This is a long way of saying I can run the command in “test” mode without having to worry about changing the original hash table. This is also why I create a clone instead of simply setting the value of my temporary hash table to the original hash table. When you do that, the two variables are actually linked so any changes in one are made to other. Here’s an example of what I’m talking about:

PS C:\> $a = @{A=1;B=2;C=3}
PS C:\> $a

Name                           Value
----                           -----
A                              1
B                              2
C                              3

PS C:\> $b=$a
PS C:\> $b

Name                           Value
----                           -----
A                              1
B                              2
C                              3

PS C:\> $b.Add("D",4)
PS C:\> $b

Name                           Value
----                           -----
A                              1
B                              2
D                              4
C                              3

PS C:\> $a

Name                           Value
----                           -----
A                              1
B                              2
D                              4
C                              3

Since I want my temp copy to be “unattached”, I use the hashtable’s Clone() method. From here, it is simply a matter of creating the new key with the old value, and removing the old key with a little error handling along the way.

if ($tempHash.Contains($key)) {
    #create a key with the new name using the value from the old key
    Write-Verbose "Adding new key $newKey to the temporary hashtable"
    $tempHash.Add($NewKey,$tempHash.$Key)
    #remove the old key
    Write-Verbose "Removing $key"
    $tempHash.Remove($Key)
    #write the new value to the variable
    Write-Verbose "Writing the new hashtable"
    Write-Verbose ($tempHash | out-string)
    Set-Variable -Name $Name -Value $tempHash -Scope $Scope -Force -PassThru:$Passthru |
    Select-Object -ExpandProperty Value
}

At the end of the process I use Set-Variable to update my original hashtable variable. By default the cmdlet doesn’t write anything to the pipeline but on the chance I might need the output, I use -Passthru and send the variable value, which is the revised hashtable, back to the pipeline. With this function I can now do something like this very easily.

PS C:\> $hash = @{name="Serenity";Class="AntivirusProduct";Namespace="root\securitycenter2"}
PS C:\> $hash

Name                           Value
----                           -----
name                           Serenity
Class                          AntivirusProduct
Namespace                      root\securitycenter2

PS C:\> Rename-HashTable -Name hash -Key name -NewKey Computername
PS C:\> $hash

Name                           Value
----                           -----
Class                          AntivirusProduct
Namespace                      root\securitycenter2
Computername                   Serenity

Now $hash has the right key names for splatting, or whatever else I have in mind. This function can come in handy with ConvertTo-HashTable. Download Rename-Hashtable and as always I hope you’ll let me know what you think.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati

About the Author

PowerShell.org Announcer

This is the official account for PowerShell.org and sponsor announcements.