## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx #region Function Split-ObjectList Function Split-ObjectList { <# .SYNOPSIS Provides the functionality to split a list of objects into chunks based on the specified mode. .DESCRIPTION This is the more performant method of breaking an object into chunks for various use cases. A list of hundreds of thousands of objects can be broken apart in seconds. .PARAMETER ByPercentage Places the function into "ByPercentage" mode. This will split the list of objects based on the provided percentage list. .PARAMETER PercentageList The list of percentages to split the list of objects by. .PARAMETER InputObjectList A valid list of objects that will be used for splitting. .PARAMETER ContinueOnError Ignore errors. .EXAMPLE $SplitObjectListResult = Split-ObjectList -ByPercentage -PercentageList @(5, 10, 15, 20, 25, 25) -InputObjectList (1..$(Get-Random -Minimum 100000 -Maximum 300000)) -Verbose Write-Output -InputObject ($SplitObjectListResult) .EXAMPLE $SplitObjectListParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' $SplitObjectListParameters.ByPercentage = $True $SplitObjectListParameters.PercentageList = New-Object -TypeName 'System.Collections.Generic.List[UInt32]' $SplitObjectListParameters.PercentageList.Add(5) $SplitObjectListParameters.PercentageList.Add(10) $SplitObjectListParameters.PercentageList.Add(15) $SplitObjectListParameters.PercentageList.Add(20) $SplitObjectListParameters.PercentageList.Add(25) $SplitObjectListParameters.PercentageList.Add(25) $SplitObjectListParameters.InputObjectList = 1..$(Get-Random -Minimum 100000 -Maximum 300000) $SplitObjectListParameters.Verbose = $True $SplitObjectListParameters.ContinueOnError = $False $SplitObjectListResult = Split-ObjectList @SplitObjectListParameters Write-Output -InputObject ($SplitObjectListResult) .NOTES If the list of input objects cannot be divided evenly, the stragglers will be automatically added to the final wave. .LINK https://stackoverflow.com/questions/56990719/split-array-by-percentage .LINK https://www.infoworld.com/article/3666499/how-to-work-with-a-priority-queue-in-net-6.html #> [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'ByPercentage')] [OutputType([System.Management.Automation.PSObject[]])] Param ( [Parameter(Mandatory=$True, ParameterSetName = 'ByPercentage')] [ValidateNotNullOrEmpty()] [Switch]$ByPercentage, [Parameter(Mandatory=$True, ParameterSetName = 'ByPercentage')] [ValidateNotNullOrEmpty()] [System.UInt32[]]$PercentageList, [Parameter(Mandatory=$True)] [ValidateNotNullOrEmpty()] [System.Object[]]$InputObjectList, [Parameter(Mandatory=$False)] [Switch]$ContinueOnError ) Begin { Try { $DateTimeLogFormat = 'dddd, MMMM dd, yyyy @ hh:mm:ss.FFF tt' ###Monday, January 01, 2019 @ 10:15:34.000 AM### [ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)} $DateTimeMessageFormat = 'MM/dd/yyyy HH:mm:ss.FFF' ###03/23/2022 11:12:48.347### [ScriptBlock]$GetCurrentDateTimeMessageFormat = {(Get-Date).ToString($DateTimeMessageFormat)} $DateFileFormat = 'yyyyMMdd' ###20190403### [ScriptBlock]$GetCurrentDateFileFormat = {(Get-Date).ToString($DateFileFormat)} $DateTimeFileFormat = 'yyyyMMdd_HHmmss' ###20190403_115354### [ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)} $TextInfo = (Get-Culture).TextInfo $LoggingDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' $LoggingDetails.Add('LogMessage', $Null) $LoggingDetails.Add('WarningMessage', $Null) $LoggingDetails.Add('ErrorMessage', $Null) $CommonParameterList = New-Object -TypeName 'System.Collections.Generic.List[String]' $CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::CommonParameters) $CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::OptionalCommonParameters) [ScriptBlock]$ErrorHandlingDefinition = { Param ( [Int16]$Severity, [Boolean]$ContinueOnError ) $ExceptionPropertyDictionary = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' $ExceptionPropertyDictionary.Add('Message', $_.Exception.Message) $ExceptionPropertyDictionary.Add('Category', $_.Exception.ErrorRecord.FullyQualifiedErrorID) #$ExceptionPropertyDictionary.Add('ExitCode', $Script:LASTEXITCODE) #$ExceptionPropertyDictionary.Add('Script', $_.InvocationInfo.ScriptName) $ExceptionPropertyDictionary.Add('LineNumber', $_.InvocationInfo.ScriptLineNumber) $ExceptionPropertyDictionary.Add('LinePosition', $_.InvocationInfo.OffsetInLine) $ExceptionPropertyDictionary.Add('Code', $_.InvocationInfo.Line.Trim()) $ExceptionMessageList = New-Object -TypeName 'System.Collections.Generic.List[String]' ForEach ($ExceptionProperty In $ExceptionPropertyDictionary.GetEnumerator()) { $ExceptionMessageList.Add("[$($ExceptionProperty.Key): $($ExceptionProperty.Value)]") } $LogMessageParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' $LogMessageParameters.Message = $ExceptionMessageList -Join ' ' $LogMessageParameters.Verbose = $True Switch ($Severity) { {($_ -in @(1))} {Write-Verbose @LogMessageParameters} {($_ -in @(2))} {Write-Warning @LogMessageParameters} {($_ -in @(3))} {Write-Error @LogMessageParameters} } Switch ($ContinueOnError) { {($_ -eq $False)} { Throw } } } #Determine the date and time we executed the function $FunctionStartTime = (Get-Date) [String]$FunctionName = $MyInvocation.MyCommand [System.IO.FileInfo]$InvokingScriptPath = $MyInvocation.PSCommandPath [System.IO.DirectoryInfo]$InvokingScriptDirectory = $InvokingScriptPath.Directory.FullName [System.IO.FileInfo]$FunctionPath = "$($InvokingScriptDirectory.FullName)\Functions\$($FunctionName).ps1" [System.IO.DirectoryInfo]$FunctionDirectory = "$($FunctionPath.Directory.FullName)" $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($FunctionName)`' is beginning. Please Wait..." Write-Verbose -Message ($LoggingDetails.LogMessage) #Define Default Action Preferences $ErrorActionPreference = 'Stop' [String[]]$AvailableScriptParameters = (Get-Command -Name ($FunctionName)).Parameters.GetEnumerator() | Where-Object {($_.Value.Name -inotin $CommonParameterList)} | ForEach-Object {"-$($_.Value.Name):$($_.Value.ParameterType.Name)"} $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Available Function Parameter(s) = $($AvailableScriptParameters -Join ', ')" Write-Verbose -Message ($LoggingDetails.LogMessage) [String[]]$SuppliedScriptParameters = $PSBoundParameters.GetEnumerator() | ForEach-Object {Try {"-$($_.Key):$($_.Value.GetType().Name)"} Catch {"-$($_.Key):Unknown"}} $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Supplied Function Parameter(s) = $($SuppliedScriptParameters -Join ', ')" Write-Verbose -Message ($LoggingDetails.LogMessage) $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($FunctionName) began on $($FunctionStartTime.ToString($DateTimeLogFormat))" Write-Verbose -Message ($LoggingDetails.LogMessage) [String]$ParameterSetName = $PSCmdlet.ParameterSetName $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Parameter Set Name: $($ParameterSetName)" Write-Verbose -Message ($LoggingDetails.LogMessage) #Create an object that will contain the functions output. $OutputObjectList = New-Object -TypeName 'System.Collections.Generic.List[System.Management.Automation.PSObject]' } Catch { $ErrorHandlingDefinition.Invoke(2, $ContinueOnError.IsPresent) } Finally { } } Process { Try { Switch ($ParameterSetName) { {($_ -iin @('ByPercentage'))} { [UInt32]$RequiredPercentageListTotal = 100 [UInt32]$PercentageListTotal = ($PercentageList | Measure-Object -Sum).Sum Switch ($PercentageListTotal -ne $RequiredPercentageListTotal) { {($_ -eq $True)} { $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The percentage list must equal $($RequiredPercentageListTotal)." Write-Warning -Message ($LoggingDetails.WarningMessage) -Verbose Return } } $InputObjectQueue = New-Object -TypeName 'System.Collections.Generic.Queue[System.Object]' $InputObjectList | ForEach-Object {($InputObjectQueue.Enqueue($_))} $InputObjectListCount = ($InputObjectList | Measure-Object).Count $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Input Object List Count: $($InputObjectListCount)" Write-Verbose -Message ($LoggingDetails.LogMessage) $PercentageListCounter = 1 $PercentageListCount = ($PercentageList | Measure-Object).Count $PercentageListUpperBound = $PercentageList.GetUpperBound(0) For ($PercentageListIndex = 0; $PercentageListIndex -lt $PercentageListCount; $PercentageListIndex++) { $Percentage = $PercentageList[$PercentageListIndex] $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to process percentage list entry $($PercentageListCounter) of $($PercentageListCount). Please Wait..." Write-Verbose -Message ($LoggingDetails.LogMessage) $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Percentage: $($Percentage)%" Write-Verbose -Message ($LoggingDetails.LogMessage) Switch ($InputObjectQueue.Count -gt 0) { {($_ -eq $True)} { $InputObjectPercentageFormula = {[System.Math]::Floor((($InputObjectListCount * $Percentage) / 100))} $InputObjectPercentageCalculation = $InputObjectPercentageFormula.InvokeReturnAsIs() $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Percentage Calculation: $($InputObjectPercentageCalculation)" Write-Verbose -Message ($LoggingDetails.LogMessage) $OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' $OutputObjectProperties.Wave = $PercentageListCounter $OutputObjectProperties.Percentage = $Percentage $OutputObjectProperties.MemberCount = 0 $OutputObjectProperties.Members = New-object -TypeName 'System.Collections.Generic.List[System.Object]' $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to process wave $($OutputObjectProperties.Wave). Please Wait..." Write-Verbose -Message ($LoggingDetails.LogMessage) $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to add $($InputObjectPercentageCalculation) member(s) to wave $($OutputObjectProperties.Wave). Please Wait..." Write-Verbose -Message ($LoggingDetails.LogMessage) $InputObjectCounter = 1 For ($InputObjectCounter = 1; $InputObjectCounter -le $InputObjectPercentageCalculation; $InputObjectCounter++) { $InputObject = $InputObjectQueue.Dequeue() $OutputObjectProperties.Members.Add($InputObject) } Switch ($PercentageListIndex) { {($_ -eq $PercentageListUpperBound)} { Switch ($InputObjectQueue.Count -gt 0) { {($_ -eq $True)} { $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The $($InputObjectListCount) input object(s) could not be divided evenly." Write-Verbose -Message ($LoggingDetails.LogMessage) $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to add the $($InputObjectQueue.Count) remaining queue member(s) to wave $($OutputObjectProperties.Wave). Please Wait..." Write-Verbose -Message ($LoggingDetails.LogMessage) While ($InputObjectQueue.Count -gt 0) { $InputObject = $InputObjectQueue.Dequeue() $OutputObjectProperties.Members.Add($InputObject) } $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - New Wave Member Count: $($OutputObjectProperties.Members.Count)" Write-Verbose -Message ($LoggingDetails.LogMessage) } } } } $OutputObjectProperties.Members = $OutputObjectProperties.Members.ToArray() $OutputObjectProperties.MemberCount = $OutputObjectProperties.Members.Count $OutputObject = New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($OutputObjectProperties) $OutputObjectList.Add($OutputObject) $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Successfully completed the processing of wave $($OutputObjectProperties.Wave)." Write-Verbose -Message ($LoggingDetails.LogMessage) } } $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - There are now $($InputObjectQueue.Count) objects remaining in the queue." Write-Verbose -Message ($LoggingDetails.LogMessage) $PercentageListCounter++ } } } } Catch { $ErrorHandlingDefinition.Invoke(2, $ContinueOnError.IsPresent) } Finally { } } End { Try { #Determine the date and time the function completed execution $FunctionEndTime = (Get-Date) $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($FunctionName) ended on $($FunctionEndTime.ToString($DateTimeLogFormat))" Write-Verbose -Message ($LoggingDetails.LogMessage) #Log the total script execution time $FunctionExecutionTimespan = New-TimeSpan -Start ($FunctionStartTime) -End ($FunctionEndTime) $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function execution took $($FunctionExecutionTimespan.Hours.ToString()) hour(s), $($FunctionExecutionTimespan.Minutes.ToString()) minute(s), $($FunctionExecutionTimespan.Seconds.ToString()) second(s), and $($FunctionExecutionTimespan.Milliseconds.ToString()) millisecond(s)" Write-Verbose -Message ($LoggingDetails.LogMessage) $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($FunctionName)`' is completed." Write-Verbose -Message ($LoggingDetails.LogMessage) } Catch { $ErrorHandlingDefinition.Invoke(2, $ContinueOnError.IsPresent) } Finally { $OutputObjectList = $OutputObjectList.ToArray() Write-Output -InputObject ($OutputObjectList) } } } #endregion