diff --git a/Split-ObjectList.ps1 b/Split-ObjectList.ps1 new file mode 100644 index 0000000..806035c --- /dev/null +++ b/Split-ObjectList.ps1 @@ -0,0 +1,330 @@ +## 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. + + .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..200000) -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..200000 + $SplitObjectListParameters.Verbose = $True + $SplitObjectListParameters.ContinueOnError = $False + + $SplitObjectListResult = Split-ObjectList @SplitObjectListParameters + + Write-Output -InputObject ($SplitObjectListResult) + + .NOTES + Most of the time, various "List" type objects are used with index removal and so forth, which can work, but is not the most performant. + + .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 + + 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]::Round((($InputObjectListCount * $Percentage) / 100), 0)} + + $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.Count = 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) + } + + $OutputObjectProperties.Members = $OutputObjectProperties.Members.ToArray() + + $OutputObjectProperties.Count = $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 \ No newline at end of file