Created
February 2, 2022 02:46
-
-
Save JustinGrote/5b7893b4e84bee3c94754e85fc35526f to your computer and use it in GitHub Desktop.
A demonstration of using Write-Progress in a thread-safe way inside Foreach-Parallel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using namespace System.Collections.Concurrent | |
using namespace System.Collections.Generic | |
function Get-GroupCompareInfo { | |
[OutputType([SortedDictionary[string, string[]]])] | |
<# | |
.SYNOPSIS | |
Creates a simple dictionary useful for comparing groups across tenants | |
.DESCRIPTION | |
This boils down a list of groups supplied by GroupID to a dictionary containing the unique alias of the group and the left-side UPN of the users | |
This is useful when comparing to another directory to make sure the groups are the same | |
#> | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline)][Guid]$GroupId | |
) | |
begin { | |
#We collect the group Ids so we can process them in parallel | |
[Hashset[Guid]]$groupIds = @() | |
} | |
process { | |
$isUnique = $groupIds.Add($GroupId) | |
if (-not $isUnique) { Write-Warning "$groupId is a duplicate entry, skipping..." } | |
} | |
end { | |
#Track progress | |
[int]$progressIndicator = 0 | |
[int]$progressId = Get-Random | |
$progressIndicatorRef = [ref]::new($progressIndicator) | |
#We have to do a lot of lookups of members, this will allow for a simple deduplication of those members to improve performance | |
$memberCache = [ConcurrentDictionary[guid, string]]::new() | |
#A simple table with the mail alias of the group and the shortname of the members. This is for easy comparison with the other tenant | |
$groupMembers = [ConcurrentDictionary[string, string[]]]::new() | |
Write-Progress -Id $progressId -Activity 'Gathering Group Comparison Info' | |
$groupIds | ForEach-Object -Verbose -Throttle 10 -Parallel { | |
param ( | |
$memberCache = $USING:memberCache, | |
$groupMembers = $USING:groupMembers, | |
$parentProgressId = $USING:progressId, | |
$progressIndicatorRef = $USING:progressIndicatorRef, | |
$groupIdCount = $USING:groupIds.Count | |
) | |
#Optimize load time by only importing the functions we need | |
Import-Module Microsoft.Graph.Users, Microsoft.Graph.Groups -Function 'Get-MgUser', 'Get-MgGroup', 'Get-MgGroupMember' | |
$progressParams = @{ | |
Id = (Get-Random) | |
ParentId = $parentProgressId | |
Activity = 'Building Membership Table: ' + $PSItem | |
} | |
try { | |
$DebugPreference = 'continue' | |
[Guid]$groupId = $PSItem | |
Write-Progress @progressParams -Status 'Getting Group Name' | |
[String]$groupName = (Get-MgGroup -GroupId $groupId -Property mailNickname).mailNickname | |
$progressParams.Activity = "Building Membership Table: $groupName ($PSItem)" | |
[Guid[]]$memberIds = (Get-MgGroupMember -GroupId $groupId).id | |
$i = 0 | |
$groupMembers[$groupName] = $memberIds | ForEach-Object { | |
Write-Progress @progressParams -Status "Resolving $PSItem to Username" -PercentComplete ($i / $memberIds.count * 100) | |
$cached = $memberCache.ContainsKey($PSItem) | |
Write-Debug "$PSItem Cache $($cached ? 'Hit' : 'Miss')" | |
$userResult = if ($cached) { | |
Write-Output $memberCache[$PSItem] | |
} else { | |
[string]$username = (Get-MgUser -UserId $PSItem -Property userprincipalname).userprincipalname.split('@')[0] | |
if (-not $username) { throw "User with GUID $PSItem doesnt exist. This is probably a bug" } | |
$memberCache[$PSItem] = $username | |
Write-Output $username | |
} | |
$i++ | |
return $userResult | |
} | |
} catch { throw } finally { | |
Write-Progress @progressParams -Completed | |
#Increment progress in a thread safe way | |
[void][Threading.Interlocked]::Increment($progressIndicatorRef) | |
Write-Progress -Id $parentProgressId -Activity 'Gathering Group Comparison Info' -Status "$groupName ($groupId) completed [$($progressIndicatorRef.Value) of $groupIdCount]" -PercentComplete ($progressIndicatorRef.Value / $groupIdCount * 100) | |
Start-Sleep 10 | |
} | |
} | |
Write-Progress -Id $progressId -Activity 'Done' -Completed | |
return [SortedDictionary[string, string[]]]$groupMembers | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Demo:
Capture.mp4