modules/Azure/Infrastructure/Private/InvokeCIEMParallelForEach.ps1

function InvokeCIEMParallelForEach {
    <#
    .SYNOPSIS
        Private helper that runs a scriptblock against a set of work items in parallel
        runspaces while preserving Devolutions.CIEM module state.

    .DESCRIPTION
        Wraps ForEach-Object -Parallel. Each runspace hydrates the Devolutions.CIEM
        module exactly once (guarded by Get-Module + a $script:CIEMRunspaceInitialized
        flag) and copies the caller's auth context into script scope before invoking
        the user-supplied scriptblock.

        Returns one record per input item with Success/Result/Error fields so the
        caller can project failures without cross-runspace exception flow.

        NOTE: Import-Module is called WITHOUT -Force. Using -Force would rebind any
        module-defined classes (e.g., [CIEMAzureAuthContext]) and break ::new() calls
        inside the runspace, and would also re-import on every item instead of once
        per runspace.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject[]])]
    param(
        [Parameter(ValueFromPipeline)]
        [object[]]$InputObject,

        [Parameter()]
        [int]$ThrottleLimit = $script:CIEMParallelThrottleLimitDiscovery,

        [Parameter(Mandatory)]
        [scriptblock]$ScriptBlock
    )

    begin {
        $ErrorActionPreference = 'Stop'
        $items = [System.Collections.Generic.List[object]]::new()
    }

    process {
        foreach ($item in $InputObject) {
            $items.Add($item)
        }
    }

    end {
        if ($items.Count -eq 0) {
            return @()
        }

        $module = Get-Module Devolutions.CIEM
        if (-not $module) {
            throw 'InvokeCIEMParallelForEach requires the Devolutions.CIEM module to be loaded.'
        }

        $modulePath = $module.Path
        $authSnapshot = $script:AuthContext
        $azureAuthSnapshot = $script:AzureAuthContext
        $scriptBlockText = $ScriptBlock.ToString()

        $items | ForEach-Object -ThrottleLimit $ThrottleLimit -Parallel {
            $ErrorActionPreference = 'Stop'

            # Init-once-per-runspace: runspaces are reused across items assigned to the
            # same throttle slot. Only import/hydrate on first use in this runspace.
            if (-not $script:CIEMRunspaceInitialized) {
                if (-not (Get-Module Devolutions.CIEM)) {
                    Import-Module $using:modulePath
                }

                $moduleInstance = Get-Module Devolutions.CIEM
                & $moduleInstance {
                    param($authSnapshot, $azureAuthSnapshot)

                    $script:AuthContext = @{}
                    if ($authSnapshot) {
                        foreach ($key in $authSnapshot.Keys) {
                            $script:AuthContext[$key] = $authSnapshot[$key]
                        }
                    }

                    if ($azureAuthSnapshot) {
                        $ctx = [CIEMAzureAuthContext]::new()
                        foreach ($property in 'ProfileId', 'ProfileName', 'ProviderId', 'Method', 'TenantId', 'ClientId', 'ManagedIdentityClientId', 'AccountId', 'AccountType', 'ARMToken', 'GraphToken', 'KeyVaultToken', 'LastError') {
                            if ($azureAuthSnapshot.PSObject.Properties.Name -contains $property) {
                                $ctx.$property = $azureAuthSnapshot.$property
                            }
                        }
                        if ($azureAuthSnapshot.PSObject.Properties.Name -contains 'SubscriptionIds') {
                            $ctx.SubscriptionIds = @($azureAuthSnapshot.SubscriptionIds)
                        }
                        if ($azureAuthSnapshot.PSObject.Properties.Name -contains 'TokenExpiresAt' -and $azureAuthSnapshot.TokenExpiresAt) {
                            $ctx.TokenExpiresAt = $azureAuthSnapshot.TokenExpiresAt
                        }
                        if ($azureAuthSnapshot.PSObject.Properties.Name -contains 'ConnectedAt' -and $azureAuthSnapshot.ConnectedAt) {
                            $ctx.ConnectedAt = $azureAuthSnapshot.ConnectedAt
                        }
                        $ctx.IsConnected = $true
                        $script:AzureAuthContext = $ctx
                        $script:AuthContext['Azure'] = $ctx
                    }
                    else {
                        $script:AzureAuthContext = $null
                    }
                } $using:authSnapshot $using:azureAuthSnapshot

                $script:CIEMRunspaceInitialized = $true
            }

            $childBlock = [scriptblock]::Create($using:scriptBlockText)
            try {
                $result = @(& $childBlock $_)
                [pscustomobject]@{
                    Input   = $_
                    Success = $true
                    Result  = $result
                    Error   = $null
                }
            }
            catch {
                [pscustomobject]@{
                    Input   = $_
                    Success = $false
                    Result  = @()
                    Error   = $_.Exception.Message
                }
            }
        }
    }
}