Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
December 13, 2024

Taking It Apart with PowerShell

In the last issue, I explored techniques and concepts for putting things together with PowerShell. This time, I'm going to take things apart. There's nothing secret or special. The topic allows me to cover a variety of techniques and concepts that you might find useful in your own scripts and functions.

Arrays

To my mind, breaking apart an array is selecting specific elements from the array. The easiest way to do this is to use the index operator. The index operator is the square brackets [] that you use to access an element in an array. The index is zero-based, meaning the first element is at index 0, the second element is at index 1, and so on.

PS C:\> $a = 1..10
PS C:\> $a[1]
2

You can specify a range of items by using a range operator.

PS C:\> $a[0..3]
1
2
3
4

Or you can use a comma to specify multiple indexes.

PS C:\> $a[7,9]
8
10

What you can't do is combine ranges and individual indexes. This syntax will fail.

$a[(0..3),7,9]

You have to turn things around.

PS C:\> (0..3),7,9 | foreach {$a[$_]}
1
2
3
4
8
10

You can also start at the end of the array and work backward.

PS C:\> $a[-1]
10
PS C:\> $a[-3]
8
PS C:\> $a[-1..-4]
10
9
8
7

Remember, the array is just a group of (typically) like objects. You can always filter the array using the Where-Object cmdlet or the Where method.

PS C:\> $a | Where {$_ -ge 6}
6
7
8
9
10
PS C:\> $a.where({$_ -ge 8})
8
9
10

HashTables

HashTables are a bit more complex because they have keys and values.

$hash = @{
    Name = 'John'
    Age  = 30
    City = 'New York'
}

You can access a value by using the key.

PS C:\> $hash.name
John

The other way to "break" apart a hashtable is to enumerate the keys and values using the GetEnumerator() method.

PS C:\> $hash.GetEnumerator()

Name                           Value
----                           -----
Age                            30
Name                           John
City                           New York

This method returns an array of DictionaryEntry objects. You can then access the key and value properties.

PS C:\> $hash.GetEnumerator() | Get-Member

   TypeName: System.Collections.DictionaryEntry

Name        MemberType    Definition
----        ----------    ----------
Name        AliasProperty Name = Key
Deconstruct Method        void Deconstruct([ref] System.Object key, [ref] System.Object value)
Equals      Method        bool Equals(System.Object obj)
GetHashCode Method        int GetHashCode()
GetType     Method        type GetType()
ToString    Method        string ToString()
Key         Property      System.Object Key {get;set;}
Value       Property      System.Object Value {get;set;}

> Note the alias property of Name for the Key property.

Enumeration makes it easy to work with values or keys.

PS C:\> $hash.GetEnumerator() | Where {$_.value -is [string]} | foreach { $_.Value.ToUpper()}
JOHN
NEW YORK

Here's a practical example.

#requires -version 5.1

# Test-ProfilePerformance.ps1
<#
run this script in a new Powershell/pwsh session with no profile
$r = PowerShell -nologo -NoProfile -file C:\scripts\Test-ProfilePerformance.ps1
$r = pwsh -nologo -NoProfile -file C:\scripts\Test-ProfilePerformance.ps1

Or run it in a new Powershell/pwsh session with no profile
#>

#define a hashtable of profile paths in order of processing
$profiles = [ordered]@{
    AllUsersAllHosts       = $profile.AllUsersAllHosts
    AllUsersCurrentHost    = $profile.AllUsersCurrentHost
    CurrentUserAllHosts    = $profile.CurrentUserAllHosts
    CurrentUserCurrentHost = $profile.CurrentUserCurrentHost
}

#only need to get these values once
$PSVer = $PSVersionTable.PSVersion
$computer = [System.Environment]::MachineName
$user = "$([System.Environment]::UserDomainName)\$([system.Environment]::userName)"

foreach ($prof in $profiles.GetEnumerator()) {
    If (Test-Path $prof.value) {
        Write-Host "Measuring script for $($prof.key)" -ForegroundColor cyan
        $m = Measure-Command { . $prof.value }

        #create a result
        [PSCustomObject]@{
            Computername = $computer
            Username     = $user
            PSVersion    = $PSVer
            Profile      = $prof.key
            Path         = $prof.value
            TimeMS       = $m.totalMilliseconds
        }

        Clear-Variable -Name m
    } #if test path
    else {
        Write-Host "Skipping profile $($prof.key) because it does not exist" -ForegroundColor yellow
    }

} #foreach profile

The script is meant to be run in a new PowerShell session with no profile. It measures the time it takes to run each profile script. The script uses a hashtable to define the profile paths in the order they should be processed. The script then enumerates the hashtable and processes each profile. If the profile path doesn't exist, the script skips it. In the code you can see where I am using the Key and Value properties of the DictionaryEntry object.

PS C:\&gt; powershell.exe -nologo -NoProfile
PS C:\&gt; $r = C:\scripts\Test-ProfilePerformance.ps1
Measuring script for AllUsersAllHosts
Skipping profile AllUsersCurrentHost because it does not exist
Measuring script for CurrentUserAllHosts
Measuring script for CurrentUserCurrentHost
PS C:\&gt; $r | Select-Object Profile,TimeMS

Profile                    TimeMS
-------                    ------
AllUsersAllHosts         101.1181
CurrentUserAllHosts    10121.8216
CurrentUserCurrentHost    30.3333

Yeah, I do a lot with my profiles.

Want to read the full issue?
GitHub Bluesky LinkedIn About Jeff
Powered by Buttondown, the easiest way to start and grow your newsletter.