Reputation: 1
I have a foreach loop that should collect all of the user profiles in one PSCustomObject for each computer and display it. But that's not what happens it loops but all of the profiles are displayed in one and the same PSCustomObject and all show in the last output.
Example; If I want to display all the user profiles for Computer1 and Computer2 it displays all of the user profiles in the Computer2 row and nothing on the Computer1 row. It merge all information into the last display. See picture for more clarity.
Function Get-UserProfiles {
[CmdletBinding()]
Param(
[string]$ComputerName = "localhost",
[array]$ExcludedProfiles
)
foreach ($Computer in $ComputerName.Split(",").Trim()) {
Write-Host "`n== All profiles on $($Computer) ==`n"
try {
Get-CimInstance -ComputerName $Computer -className Win32_UserProfile | Where-Object { (-Not ($_.Special)) } | Foreach-Object {
if (-Not ($_.LocalPath.split('\')[-1] -in $ExcludedProfiles)) {
[PSCustomObject]@{
'UserName' = $_.LocalPath.split('\')[-1]
'Profile path' = $_.LocalPath
'Last used' = ($_.LastUseTime -as [DateTime]).ToString("yyyy-MM-dd HH:mm")
'Is the profile active?' = $_.Loaded
}
}
}
}
catch {
Write-Host "$($PSItem.Exception)"
break
}
}
}
Picture that shows the output and what I mean
Upvotes: 0
Views: 557
Reputation: 27806
The observed behavior is due to delayed output behaviour of Format-Table
, which is implicitly used by your code. See this answer for an in-depth explanation.
Here is a simplified example, that when pasted into the console, exhibits the same behavior as your code:
Write-Host "== first =="; [pscustomobject] @{ one = '1a'; two = '2a'; three = '3a' }; Write-Host "== second =="; [pscustomobject] @{ one = '1b'; two = '2b'; three = '3b' }
Output:
== first ==
== second ==
one two three
--- --- -----
1a 2a 3a
1b 2b 3b
To fix the code at hand, simply use Format-Table
explicitly:
Function Get-UserProfiles {
[CmdletBinding()]
Param(
[string]$ComputerName = "localhost",
[array]$ExcludedProfiles
)
foreach ($Computer in $ComputerName.Split(",").Trim()) {
Write-Host "`n== All profiles on $($Computer) ==`n"
try {
Get-CimInstance -ComputerName $Computer -className Win32_UserProfile | Where-Object { (-Not ($_.Special)) } | Foreach-Object {
if (-Not ($_.LocalPath.split('\')[-1] -in $ExcludedProfiles)) {
[PSCustomObject]@{
'UserName' = $_.LocalPath.split('\')[-1]
'Profile path' = $_.LocalPath
'Last used' = ($_.LastUseTime -as [DateTime]).ToString("yyyy-MM-dd HH:mm")
'Is the profile active?' = $_.Loaded
}
}
} | Format-Table # <<<<<<< ADDED <<<<<<<
}
catch {
Write-Host "$($PSItem.Exception)"
break
}
}
While this should succeed in producing the desired output, it is not a good practice to intermingle code that creates objects with code that formats objects for display purposes only, such as Format-Table
and Write-Host
. You end up with an inflexible function that is "closed", as it cannot be used for further processing of the data (well, not impossible, but you'd have to parse the data in its displayed form, which is cumbersome and error-prone).
Instead, focus on the data and let the function output only objects. Think about how to display these objects later. The formatting could be done by another function, piped to the function that produces the objects.
Simplified example (NOT recommended):
Function Get-MyObjects {
Write-Host "== first =="
[pscustomobject] @{ one = '1a'; two = '2a'; three = '3a' } | Format-Table
Write-Host "== second =="
[pscustomobject] @{ one = '1b'; two = '2b'; three = '3b' } | Format-Table
}
# Create and display the objects, no further processing possible
Get-MyObjects
Simplified example (recommended):
Function Get-MyObjects {
# This outputs an array of two objects.
# Note how the headers 'FIRST' and 'SECOND' have become data as well!
[pscustomobject] @{ group = 'FIRST'; one = '1a'; two = '2a'; three = '3a' }
[pscustomobject] @{ group = 'SECOND'; one = '1b'; two = '2b'; three = '3b' }
}
# Create objects, then pipe to `Format-Table` for display.
Get-MyObjects | Format-Table -GroupBy group -Property one, two, three
# We can still process the objects further:
$objects = Get-MyObjects
$objects | Where-Object group -eq FIRST | ForEach-Object { $_.one = 'foo' }
$objects | Format-Table -GroupBy group -Property one, two, three
Upvotes: 2