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:\> powershell.exe -nologo -NoProfile
PS C:\> $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:\> $r | Select-Object Profile,TimeMS
Profile TimeMS
------- ------
AllUsersAllHosts 101.1181
CurrentUserAllHosts 10121.8216
CurrentUserCurrentHost 30.3333
Yeah, I do a lot with my profiles.