PoshToolbox.psm1

#region: Exceptions
#region: ./src/Exceptions/New-ActiveDirectoryObjectNotFoundException.ps1
function New-ActiveDirectoryObjectNotFoundException {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Function does not change system state.')]

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ErrorRecord])]

    ## PARAMETERS #############################################################
    param(
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Message,

        [Parameter()]
        [switch]
        $Throw
    )

    ## PROCESS ################################################################
    process {
        $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
            [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException]::new($Message),
            "System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException",
            [System.Management.Automation.ErrorCategory]::ObjectNotFound,
            $null
        )

        if ($Throw) {
            throw $ErrorRecord
        }

        Write-Output $ErrorRecord
    }
}
#endregion
#region: ./src/Exceptions/New-ActiveDirectoryOperationException.ps1
function New-ActiveDirectoryOperationException {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Function does not change system state.')]

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ErrorRecord])]

    ## PARAMETERS #############################################################
    param(
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Message,

        [Parameter()]
        [switch]
        $Throw
    )

    ## PROCESS ################################################################
    process {
        $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
            [System.DirectoryServices.ActiveDirectory.ActiveDirectoryOperationException]::new($Message),
            "System.DirectoryServices.ActiveDirectory.ActiveDirectoryOperationException",
            [System.Management.Automation.ErrorCategory]::InvalidOperation,
            $null
        )

        if ($Throw) {
            throw $ErrorRecord
        }

        Write-Output $ErrorRecord
    }
}
#endregion
#region: ./src/Exceptions/New-ActiveDirectoryServerDownException.ps1
function New-ActiveDirectoryServerDownException {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Function does not change system state.')]

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ErrorRecord])]

    ## PARAMETERS #############################################################
    param(
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Message,

        [Parameter()]
        [switch]
        $Throw
    )

    ## PROCESS ################################################################
    process {
        $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
            [System.DirectoryServices.ActiveDirectory.ActiveDirectoryServerDownException]::new($Message),
            "System.DirectoryServices.ActiveDirectory.ActiveDirectoryServerDownException",
            [System.Management.Automation.ErrorCategory]::ResourceUnavailable,
            $null
        )

        if ($Throw) {
            throw $ErrorRecord
        }

        Write-Output $ErrorRecord
    }
}
#endregion
#region: ./src/Exceptions/New-ArgumentException.ps1
function New-ArgumentException {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Function does not change system state.')]

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ErrorRecord])]

    ## PARAMETERS #############################################################
    param(
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Message,

        [Parameter()]
        [switch]
        $Throw
    )

    ## PROCESS ################################################################
    process {
        $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
            [System.ArgumentException]::new($Message),
            "System.ArgumentException",
            [System.Management.Automation.ErrorCategory]::InvalidArgument,
            $null
        )

        if ($Throw) {
            throw $ErrorRecord
        }

        Write-Output $ErrorRecord
    }
}
#endregion
#region: ./src/Exceptions/New-InvalidOperationException.ps1
function New-InvalidOperationException {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Function does not change system state.')]

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ErrorRecord])]

    ## PARAMETERS #############################################################
    param(
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Message,

        [Parameter()]
        [switch]
        $Throw
    )

    ## PROCESS ################################################################
    process {
        $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
            [System.InvalidOperationException]::new($Message),
            "System.InvalidOperationException",
            [System.Management.Automation.ErrorCategory]::ConnectionError,
            $null
        )

        if ($Throw) {
            throw $ErrorRecord
        }

        Write-Output $ErrorRecord
    }
}
#endregion
#region: ./src/Exceptions/New-LimitException.ps1
function New-LimitException {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Function does not change system state.')]

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ErrorRecord])]

    ## PARAMETERS #############################################################
    param(
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Message,

        [Parameter()]
        [switch]
        $Throw
    )

    ## PROCESS ################################################################
    process {
        $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
            [PoshToolbox.LimitException]::new($Message),
            "PoshToolbox.LimitException",
            [System.Management.Automation.ErrorCategory]::LimitsExceeded,
            $null
        )

        if ($Throw) {
            throw $ErrorRecord
        }

        Write-Output $ErrorRecord
    }
}
#endregion
#region: ./src/Exceptions/New-MethodInvocationException.ps1
function New-MethodInvocationException {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Function does not change system state.')]

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ErrorRecord])]

    ## PARAMETERS #############################################################
    param(
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [ValidateNotNullOrEmpty()]
        [System.Exception]
        $Exception,

        [Parameter()]
        [switch]
        $Throw
    )

    ## PROCESS ################################################################
    process {
        $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
            $Exception,
            "System.Management.Automation.MethodInvocationException",
            [System.Management.Automation.ErrorCategory]::InvalidOperation,
            $null
        )

        if ($Throw) {
            throw $ErrorRecord
        }

        Write-Output $ErrorRecord
    }
}
#endregion
#region: ./src/Exceptions/New-PSInvalidOperationException.ps1
function New-PSInvalidOperationException {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Function does not change system state.')]

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ErrorRecord])]

    ## PARAMETERS #############################################################
    param(
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Message,

        [Parameter()]
        [switch]
        $Throw
    )

    ## PROCESS ################################################################
    process {
        $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
            [System.Management.Automation.PSInvalidOperationException]::new($Message),
            "System.Management.Automation.PSInvalidOperationException",
            [System.Management.Automation.ErrorCategory]::InvalidOperation,
            $null
        )

        if ($Throw) {
            throw $ErrorRecord
        }

        Write-Output $ErrorRecord
    }
}
#endregion
#region: ./src/Exceptions/New-UnauthorizedAccessException.ps1
function New-UnauthorizedAccessException {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Function does not change system state.')]

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ErrorRecord])]

    ## PARAMETERS #############################################################
    param(
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Message,

        [Parameter()]
        [switch]
        $Throw
    )

    ## PROCESS ################################################################
    process {
        $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
            [System.UnauthorizedAccessException]::new($Message),
            "System.UnauthorizedAccessException",
            [System.Management.Automation.ErrorCategory]::PermissionDenied,
            $null
        )

        if ($Throw) {
            throw $ErrorRecord
        }

        Write-Output $ErrorRecord
    }
}
#endregion
#endregion
#region: Public
#region: ./src/Public/ConvertFrom-Base64String.ps1
function ConvertFrom-Base64String {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [CmdletBinding()]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Mandatory,
            ValueFromPipeline
        )]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $InputObject
    )

    ## PROCESS ################################################################
    process {
        foreach ($Object in $InputObject) {
            try {
                $Bytes = [System.Convert]::FromBase64String($Object)

                try {
                    $String = -join [char[]] $Bytes

                    Write-Output ([System.Management.Automation.PSSerializer]::Deserialize($String))
                } catch {
                    Write-Output $Bytes
                }

                ## EXCEPTIONS #################################################
            } catch [System.Management.Automation.MethodInvocationException] {
                $PSCmdlet.WriteError(( New-MethodInvocationException -Exception $_.Exception.InnerException ))
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }
}
#endregion
#region: ./src/Public/ConvertTo-Base64String.ps1
function ConvertTo-Base64String {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [CmdletBinding()]
    [OutputType([string])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Mandatory,
            ValueFromPipeline
        )]
        [ValidateNotNullOrEmpty()]
        [object]
        $InputObject,

        [Parameter()]
        [int32]
        $Depth = 2
    )

    ## PROCESS ################################################################
    process {
        try {
            try {
                $Bytes = [byte[]] $InputObject
            } catch {
                $Serialize = [System.Management.Automation.PSSerializer]::Serialize($InputObject, $Depth)
                $Bytes = [byte[]] $Serialize.ToCharArray()
            }

            Write-Output ([System.Convert]::ToBase64String($Bytes))

            ## EXCEPTIONS #################################################
        } catch [System.Management.Automation.MethodInvocationException] {
            $PSCmdlet.WriteError(( New-MethodInvocationException -Exception $_.Exception.InnerException ))
        } catch {
            $PSCmdlet.WriteError($_)
        }
    }
}
#endregion
#region: ./src/Public/Find-NlMtu.ps1
function Find-NlMtu {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSPossibleIncorrectUsageOfAssignmentOperator', '', Justification='Assignment Operator is intended.')]

    [CmdletBinding()]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Alias("Hostname", "IPAddress", "Address")]
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $ComputerName,

        [Parameter()]
        [ValidateRange(1, [int32]::MaxValue)]
        [int32]
        $Timeout = 10000,

        [Alias("Ttl", "TimeToLive", "Hops")]
        [Parameter()]
        [ValidateRange(1, [int32]::MaxValue)]
        [int32]
        $MaxHops = 128
    )

    ## BEGIN ##################################################################
    begin {
        $PingOptions = [System.Net.NetworkInformation.PingOptions]::new($MaxHops, $true)
    }

    ## PROCESS ################################################################
    process {
        foreach ($Computer in $ComputerName) {
            try {
                Use-Object ($Ping = [System.Net.NetworkInformation.Ping]::new()) {
                    $UpperBound = 65500
                    $LowerBound = 1

                    $Size = 9000
                    $Buffer = [byte[]]::new($Size)

                    $Result = [System.Collections.Generic.List[System.Net.NetworkInformation.PingReply]]::new()

                    while ($Size -ne $LowerBound) {
                        try {
                            Write-Verbose ("PING {0} with {1}-byte payload" -f $Computer, $Size)
                            $Reply = $Ping.Send($Computer, $Timeout, $Buffer, $PingOptions)
                        } catch {
                            New-InvalidOperationException -Message ("Connection to '{0}' failed." -f $Computer) -Throw
                        }

                        switch ($Reply.Status) {
                            "PacketTooBig" { $UpperBound = $Size }
                            "Success" { $LowerBound = $Size }
                            "TimedOut" { $UpperBound = $Size }
                            default {
                                New-InvalidOperationException -Message ("Connection to '{0}' failed with status '{1}.'" -f $Computer, $Reply.Status) -Throw
                            }
                        }

                        $Result.Add($Reply)

                        if (($Size = [System.Math]::Floor(($LowerBound + $UpperBound) / 2)) -eq 1) {
                            New-InvalidOperationException -Message ("Connection to '{0}' failed with status 'NoReply.'" -f $Computer) -Throw
                        }

                        [array]::Resize([ref] $Buffer, $Size)
                    }

                    if (($Hops = $MaxHops - $Result.Where({ $_.Status -eq "Success" })[-1].Options.Ttl) -lt 0) {
                        $Hops = 0
                    }

                    Write-Output ([pscustomobject] @{
                            ComputerName = $Computer
                            ReplyFrom    = $Result.Where({ $_.Status -eq "Success" })[-1].Address
                            "Time(ms)"   = [int] ($Result.Where({ $_.Status -eq "Success" }).RoundtripTime | Measure-Object -Average).Average
                            Hops         = $Hops
                            # IP Header (20 bytes) + ICMP Header (8 bytes) = 28 bytes
                            MTU          = $Size + 28
                        })
                }

                ## EXCEPTIONS #################################################
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }

    ## END ####################################################################
    end {
    }
}
#endregion
#region: ./src/Public/Get-ADServiceAccountCredential.ps1
function Get-ADServiceAccountCredential {
    # Copyright (c) 2021 Ryan Ephgrave, https://github.com/Ryan2065/gMSACredentialModule
    # Modified "Get-GMSACredential.ps1" by Anthony J. Raymond
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification='Retrieved password is plaintext.')]

    [CmdletBinding()]
    [OutputType([pscredential])]

    ## PARAMETERS #############################################################
    param (
        [Alias("distinguishedName", "objectGuid", "objectSid", "sAMAccountName")]
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Identity,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Server
    )

    ## BEGIN ##################################################################
    begin {
        $Properties = @(
            @{ n = "sAMAccountName"; e = { $_.Properties."samaccountname" } }
            @{ n = "Length"; e = { $_.Properties."msds-managedpassword".Length } }
            @{ n = "ManagedPassword"; e = {
                    $Length = $_.Properties."msds-managedpassword".Length
                    Write-Output ($IntPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($Length))
                    [System.Runtime.InteropServices.Marshal]::Copy([byte[]] $_.Properties."msds-managedpassword".ForEach({ $_ }), 0, $IntPtr, $Length)
                }
            }
        )
    }

    ## PROCESS ################################################################
    process {
        foreach ($Object in $Identity) {
            try {
                try {
                    # https://ldapwiki.com/wiki/ObjectGuid
                    $ObjectGuid = ([guid] $Object).ToByteArray().ForEach({ $_.ToString("X2") }) -join "\"
                    $Filter = "(&(objectGuid=\${ObjectGuid})(ObjectCategory=msDS-GroupManagedServiceAccount))"
                } catch {
                    $Filter = "(&(|(distinguishedName={0})(objectSid={0})(sAMAccountName={1}))(ObjectCategory=msDS-GroupManagedServiceAccount))" -f $Object, ($Object -ireplace "[^$]$", "$&$")
                }

                New-Variable -Name ADServiceAccount -Option AllScope

                Use-Object ($DirectorySearcher = [System.DirectoryServices.DirectorySearcher] $Filter) {
                    if ($Server) {
                        $DirectorySearcher.SearchRoot = [System.DirectoryServices.DirectoryEntry] "LDAP://${Server}"
                    }

                    $DirectorySearcher.SearchRoot.AuthenticationType = "Sealing"
                    $DirectorySearcher.PropertiesToLoad.AddRange(@("sAMAccountName", "msDS-ManagedPassword"))

                    $ADServiceAccount = $DirectorySearcher.FindOne() | Select-Object -Property $Properties

                    if (-not $ADServiceAccount) {
                        New-ActiveDirectoryObjectNotFoundException -Message ("Cannot find an object with identity: '{0}' under: '{1}'." -f $Object, $DirectorySearcher.SearchRoot.distinguishedName) -Throw
                    } elseif ($ADServiceAccount.Length -eq 0) {
                        New-ActiveDirectoryOperationException -Message "Cannot retrieve service account password. A process has requested access to an object, but has not been granted those access rights." -Throw
                    }
                }

                # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/a9019740-3d73-46ef-a9ae-3ea8eb86ac2e
                $SecureString = ConvertTo-SecureString -String ([System.Runtime.InteropServices.Marshal]::PtrToStringUni([int64] $ADServiceAccount.ManagedPassword + 16)) -AsPlainText -Force

                Write-Output ([pscredential]::new($ADServiceAccount.sAMAccountName, $SecureString))

                ## EXCEPTIONS #################################################
            } catch [System.Management.Automation.SetValueInvocationException] {
                $PSCmdlet.WriteError(( New-ActiveDirectoryServerDownException -Message "Unable to contact the server. This may be because this server does not exist, it is currently down, or it does not have the Active Directory Services running." ))
            } catch {
                $PSCmdlet.WriteError($_)
            } finally {
                if ($ADServiceAccount) {
                    [System.Runtime.InteropServices.Marshal]::Copy([byte[]]::new($ADServiceAccount.Length), 0, $ADServiceAccount.ManagedPassword, $ADServiceAccount.Length)
                    [System.Runtime.InteropServices.Marshal]::FreeHGlobal($ADServiceAccount.ManagedPassword)

                    $ADServiceAccount = $SecureString = $null
                    $null = [System.GC]::GetTotalMemory($true)
                }
            }
        }
    }

    ## END ####################################################################
    end {
    }
}
#endregion
#region: ./src/Public/Get-FolderProperties.ps1
function Get-FolderProperties {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification='Named to match Windows context menu.')]

    [CmdletBinding()]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "Path"
        )]
        [ValidateScript({
                if (Test-Path -Path $_ -PathType Container) {
                    return $true
                }
                throw "The argument specified must resolve to a valid folder path."
            })]
        [string[]]
        $Path,

        [Alias("PSPath")]
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "LiteralPath"
        )]
        [ValidateScript({
                if (Test-Path -LiteralPath $_ -PathType Container) {
                    return $true
                }
                throw "The argument specified must resolve to a valid folder path."
            })]
        [string[]]
        $LiteralPath,

        [Parameter()]
        [ValidateSet(
            "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", # Decimal Metric (Base 10)
            "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB" # Binary IEC (Base 2)
        )]
        [string]
        $Unit = "MiB"
    )

    ## BEGIN ##################################################################
    begin {
        $Prefix = @{
            [char] "K" = 1 # kilo
            [char] "M" = 2 # mega
            [char] "G" = 3 # giga
            [char] "T" = 4 # tera
            [char] "P" = 5 # peta
            [char] "E" = 6 # exa
            [char] "Z" = 7 # zetta
            [char] "Y" = 8 # yotta
        }

        $Base = $Unit.Contains("i") | Use-Ternary { 1024 } { 1000 }
        $Divisor = [System.Math]::Pow($Base, $Prefix[$Unit[0]])
    }

    ## PROCESS ################################################################
    process {
        $Process = ($PSCmdlet.ParameterSetName -cmatch "^LiteralPath") | Use-Ternary { Resolve-PoshPath -LiteralPath $LiteralPath } { Resolve-PoshPath -Path $Path }

        foreach ($Object in $Process) {
            try {
                if ($Object.Provider.Name -ne "FileSystem") {
                    New-ArgumentException "The argument specified must resolve to a valid path on the FileSystem provider." -Throw
                }

                $Folder = [System.IO.DirectoryInfo] $Object.ProviderPath

                Write-Verbose ("GET {0}" -f $Folder)
                $Dirs = $Files = $Bytes = 0
                # https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy
                $Result = robocopy $Folder.FullName.TrimEnd("\") \\null /l /e /np /xj /r:0 /w:0 /bytes /nfl /ndl

                if (($LASTEXITCODE -eq 16) -and ($Result[-2] -eq "Access is denied.")) {
                    New-UnauthorizedAccessException -Message ("Access to the path '{0}' is denied." -f $Folder) -Throw
                } elseif ($LASTEXITCODE -eq 16) {
                    New-ArgumentException -Message ("The specified path '{0}' is invalid." -f $Folder) -Throw
                }

                switch -Regex ($Result) {
                    "Dirs :\s+(\d+)" { $Dirs = [double] $Matches[1] - 1 }
                    "Files :\s+(\d+)" { $Files = [double] $Matches[1] }
                    "Bytes :\s+(\d+)" { $Bytes = [double] $Matches[1] }
                }

                Write-Output ([pscustomobject] @{
                        FullName = $Folder.FullName
                        Length   = $Bytes
                        Size     = "{0:n2} {1}" -f ($Bytes / $Divisor), $Unit
                        Contains = "{0} Files, {1} Folders" -f $Files, $Dirs
                        Created  = "{0:F}" -f $Folder.CreationTime
                    })

                ## EXCEPTIONS #################################################
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }

    ## END ####################################################################
    end {
    }
}
#endregion
#region: ./src/Public/Invoke-ExponentialBackoff.ps1
function Invoke-ExponentialBackoff {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [CmdletBinding()]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [int]
        $RetryCount = 3,

        [Parameter()]
        [int]
        $Base = 2,

        [Parameter()]
        [int]
        $Scalar = 1
    )

    ## PROCESS ################################################################
    process {
        for ([int] $i = 0; $i -lt $RetryCount; $i++) {
            try {
                . $ScriptBlock
                break
            } catch {
                $PSCmdlet.WriteError($_)

                if (($i + 1) -ge $RetryCount) {
                    $PSCmdlet.ThrowTerminatingError(( New-LimitException -Message ("The operation has reached the limit of {0} retries." -f $RetryCount) ))
                }

                Start-Sleep -Milliseconds ((Get-Random -Maximum 1000) * $Scalar * [System.Math]::Pow($Base, $i))
            }
        }
    }
}
#endregion
#region: ./src/Public/Join-File.ps1
function Join-File {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "Path"
        )]
        [ValidateScript({
                if (Test-Path -Path $_ -PathType Leaf -Filter *.*split) {
                    return $true
                }
                throw "The argument specified must resolve to a valid split type file."
            })]
        [string[]]
        $Path,

        [Alias("PSPath")]
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "LiteralPath"
        )]
        [ValidateScript({
                if (Test-Path -LiteralPath $_ -PathType Leaf -Filter *.*split) {
                    return $true
                }
                throw "The argument specified must resolve to a valid split type file."
            })]
        [string[]]
        $LiteralPath,

        [Parameter()]
        [ValidateScript({
                if (Test-Path -LiteralPath $_ -IsValid) {
                    return $true
                }
                throw "The argument specified must resolve to a valid file or folder path."
            })]
        [string]
        $Destination = (Get-Location -PSProvider FileSystem).ProviderPath
    )

    ## BEGIN ##################################################################
    begin {
        $DestinationInfo = [System.IO.FileInfo] (Resolve-PoshPath -LiteralPath $Destination).ProviderPath
    }

    ## PROCESS ################################################################
    process {
        $Process = ($PSCmdlet.ParameterSetName -cmatch "^LiteralPath") | Use-Ternary { Resolve-PoshPath -LiteralPath $LiteralPath } { Resolve-PoshPath -Path $Path }

        foreach ($Object in $Process) {
            try {
                if ($Object.Provider.Name -ne "FileSystem") {
                    New-ArgumentException "The argument specified must resolve to a valid path on the FileSystem provider." -Throw
                }

                $File = [System.IO.FileInfo] $Object.ProviderPath

                $CalculatedDestination = $DestinationInfo.Extension | Use-Ternary { $DestinationInfo } { "{0}\{1}" -f $DestinationInfo.FullName.TrimEnd("\"), $File.BaseName }

                if ($PSCmdlet.ShouldProcess($CalculatedDestination, "Write Content")) {
                    if (-not ($Directory = $DestinationInfo.Extension | Use-Ternary { $DestinationInfo.Directory } { $DestinationInfo }).Exists) {
                        $null = [System.IO.Directory]::CreateDirectory($Directory)
                    }

                    Write-Verbose ("WRITE {0}" -f $CalculatedDestination)
                    Use-Object ($Writer = [System.IO.File]::OpenWrite($CalculatedDestination)) {
                        # sort to fix ChildItem number sorting
                        foreach ($SplitFile in (Get-ChildItem -Path ("{0}\{1}.*split" -f $File.Directory, $File.BaseName)).FullName | Sort-Object -Property @{e = { [int32] [regex]::Match($_, "\.(\d+)split$").Groups[1].Value } }) {
                            Write-Verbose ("READ {0}" -f $SplitFile)
                            $Bytes = [System.IO.File]::ReadAllBytes($SplitFile)
                            $Writer.Write($Bytes, 0, $Bytes.Length)
                        }
                    }
                }

                Write-Output (Get-ChildItem -Path $CalculatedDestination)

                ## EXCEPTIONS #################################################
            } catch [System.Management.Automation.MethodInvocationException] {
                $PSCmdlet.WriteError(( New-MethodInvocationException -Exception $_.Exception.InnerException ))
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }

    ## END ####################################################################
    end {
    }
}
#endregion
#region: ./src/Public/New-Exception.ps1
function New-Exception {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Function does not change system state.')]

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ErrorRecord])]

    ## PARAMETERS #############################################################
    param(
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Message,

        [Parameter()]
        [switch]
        $Throw
    )

    ## PROCESS ################################################################
    process {
        $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
            [System.Exception]::new($Message),
            "System.Exception",
            [System.Management.Automation.ErrorCategory]::NotSpecified,
            $null
        )

        if ($Throw) {
            throw $ErrorRecord
        }

        Write-Output $ErrorRecord
    }
}
#endregion
#region: ./src/Public/New-IPAddress.ps1
function New-IPAddress {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Function does not change system state.')]

    [CmdletBinding()]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Alias("Address")]
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipeline
        )]
        [string[]]
        $IPAddress
    )

    ## PROCESS ################################################################
    process {
        foreach ($Address in $IPAddress) {
            try {
                Write-Output ([System.Net.IPAddress]::Parse($Address))

                ## EXCEPTIONS #################################################
            } catch [System.Management.Automation.MethodInvocationException] {
                $PSCmdlet.WriteError(( New-MethodInvocationException -Exception $_.Exception.InnerException ))
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }
}
#endregion
#region: ./src/Public/New-IPSubnet.ps1
function New-IPSubnet {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Function does not change system state.')]

    [CmdletBinding()]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipeline,
            ParameterSetName = "InputObject"
        )]
        [string[]]
        $InputObject,

        [Alias("Address")]
        [Parameter(
            Mandatory,
            ParameterSetName = "IPAddress"
        )]
        [string]
        $IPAddress,

        [Alias("Prefix")]
        [Parameter(
            Mandatory,
            ParameterSetName = "IPAddress"
        )]
        [int]
        $IPPrefix
    )

    ## PROCESS ################################################################
    process {
        foreach ($Object in $PSBoundParameters[$PSCmdlet.ParameterSetName]) {
            try {
                if ($PSCmdlet.ParameterSetName -eq "InputObject") {
                    $IPAddress = ($Object -isplit "\\|\/")[0]
                    $IPPrefix = ($Object -isplit "\\|\/")[-1]
                }

                Write-Output ([System.Net.IPSubnet]::Parse($IPAddress, $IPPrefix))

                ## EXCEPTIONS #################################################
            } catch [System.Management.Automation.MethodException] {
                $PSCmdlet.WriteError(( New-MethodInvocationException -Exception $_.Exception.InnerException ))
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }
}
#endregion
#region: ./src/Public/Resolve-PoshPath.ps1
function Resolve-PoshPath {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [CmdletBinding()]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "Path"
        )]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Path,

        [Alias("PSPath")]
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "LiteralPath"
        )]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $LiteralPath
    )

    ## PROCESS ################################################################
    process {
        foreach ($PSPath in $PSBoundParameters[$PSCmdlet.ParameterSetName]) {
            try {
                if ($PSCmdlet.ParameterSetName -eq "Path") {
                    try {
                        $PSPath = $PSCmdlet.SessionState.Path.GetResolvedPSPathFromPSPath($PSPath)
                    } catch [System.Management.Automation.MethodInvocationException] {
                        $PSPath = $_.Exception.InnerException.ItemName
                    }
                }

                foreach ($String in $PSPath) {
                    $Provider = $Drive = $null
                    $ProviderPath = $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($String, [ref] $Provider, [ref] $Drive)

                    Write-Output ([pscustomobject] @{
                            ProviderPath = $ProviderPath
                            Provider     = $Provider
                            Drive        = $Drive
                        })
                }

                ## EXCEPTIONS #################################################
            } catch [System.Management.Automation.MethodInvocationException] {
                $PSCmdlet.WriteError(( New-MethodInvocationException -Exception $_.Exception.InnerException ))
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }
}
#endregion
#region: ./src/Public/Split-File.ps1
function Split-File {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "Path"
        )]
        [ValidateScript({
                if (Test-Path -Path $_ -PathType Leaf) {
                    return $true
                }
                throw "The argument specified must resolve to a valid file path."
            })]
        [string[]]
        $Path,

        [Alias("PSPath")]
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "LiteralPath"
        )]
        [ValidateScript({
                if (Test-Path -LiteralPath $_ -PathType Leaf) {
                    return $true
                }
                throw "The argument specified must resolve to a valid file path."
            })]
        [string[]]
        $LiteralPath,

        [Parameter()]
        [ValidateScript({
                if (Test-Path -LiteralPath $_ -IsValid) {
                    return $true
                }
                throw "The argument specified must resolve to a valid file or folder path."
            })]
        [string]
        $Destination = (Get-Location -PSProvider FileSystem).ProviderPath,

        [Parameter (
            Mandatory
        )]
        [ValidateRange(0, [int32]::MaxValue)]
        [int32]
        $Size
    )

    ## BEGIN ##################################################################
    begin {
        $DestinationInfo = [System.IO.FileInfo] (Resolve-PoshPath -LiteralPath $Destination).ProviderPath
    }

    ## PROCESS ################################################################
    process {
        $Process = ($PSCmdlet.ParameterSetName -cmatch "^LiteralPath") | Use-Ternary { Resolve-PoshPath -LiteralPath $LiteralPath } { Resolve-PoshPath -Path $Path }

        foreach ($Object in $Process) {
            try {
                if ($Object.Provider.Name -ne "FileSystem") {
                    New-ArgumentException "The argument specified must resolve to a valid path on the FileSystem provider." -Throw
                }

                $File = [System.IO.FileInfo] $Object.ProviderPath

                Write-Verbose ("READ {0}" -f $File)
                Use-Object ($Reader = [System.IO.File]::OpenRead($File)) {
                    $Buffer = [byte[]]::new($Size)
                    $Count = 1

                    $CalculatedDestination = $DestinationInfo.Extension | Use-Ternary { "{0}\{1}" -f $DestinationInfo.Directory.FullName, $File.Name } { "{0}\{1}" -f $DestinationInfo.FullName.TrimEnd("\"), $File.Name }

                    while ($Read = $Reader.Read($Buffer, 0, $Buffer.Length)) {
                        if ($Read -ne $Buffer.Length) {
                            [array]::Resize([ref] $Buffer, $Read)
                        }

                        $SplitFile = "{0}.{1}split" -f $CalculatedDestination, $Count
                        if ($PSCmdlet.ShouldProcess($SplitFile, "Write Content")) {
                            if (-not ($Directory = $DestinationInfo.Extension | Use-Ternary { $DestinationInfo.Directory } { $DestinationInfo }).Exists) {
                                $null = [System.IO.Directory]::CreateDirectory($Directory)
                            }

                            Write-Verbose ("WRITE {0}" -f $SplitFile)
                            [System.IO.File]::WriteAllBytes($SplitFile, $Buffer)
                        }

                        $Count++
                    }

                    # sort to fix ChildItem number sorting
                    Write-Output (Get-ChildItem -Path ("{0}.*split" -f $CalculatedDestination) | Sort-Object -Property @{e = { [int32] [regex]::Match($_.FullName, "\.(\d+)split$").Groups[1].Value } })
                }

                ## EXCEPTIONS #################################################
            } catch [System.Management.Automation.MethodInvocationException] {
                $PSCmdlet.WriteError(( New-MethodInvocationException -Exception $_.Exception.InnerException ))
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }

    ## END ####################################################################
    end {
    }
}
#endregion
#region: ./src/Public/Start-PoshLog.ps1
function Start-PoshLog {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Justification = 'As designed to receive log events.')]

    [CmdletBinding(DefaultParameterSetName = "Path")]
    [OutputType([void])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "Path"
        )]
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "PathAppend"
        )]
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "PathNoClobber"
        )]
        [ValidateScript({
                if (Test-Path -Path $_ -IsValid) {
                    return $true
                }
                throw "The argument specified must resolve to a valid file or folder path."
            })]
        [string[]]
        $Path = [Environment]::GetFolderPath("MyDocuments"),

        [Alias("PSPath")]
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "LiteralPath"
        )]
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "LiteralPathAppend"
        )]
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "LiteralPathNoClobber"
        )]
        [ValidateScript({
                if (Test-Path -LiteralPath $_ -IsValid) {
                    return $true
                }
                throw "The argument specified must resolve to a valid file or folder path."
            })]
        [string[]]
        $LiteralPath,

        [Parameter(ParameterSetName = "PathAppend")]
        [Parameter(ParameterSetName = "LiteralPathAppend")]
        [switch]
        $Append,

        [Parameter(ParameterSetName = "PathNoClobber")]
        [Parameter(ParameterSetName = "LiteralPathNoClobber")]
        [switch]
        $NoClobber,

        [Parameter()]
        [switch]
        $AsUtc,

        [Parameter()]
        [switch]
        $PassThru
    )

    ## BEGIN ##################################################################
    begin {
        $DateTime = [datetime]::Now

        $Format = $AsUtc | Use-Ternary { "yyyy\-MM\-dd HH:mm:ss\Z", "ToUniversalTime", "yyyyMMdd\-HHmmss\Z" } { "yyyy\-MM\-dd HH:mm:ss", "ToLocalTime", "yyyyMMdd\-HHmmss" }
        $FileMode = $Append | Use-Ternary { [System.IO.FileMode]::Append } { $NoClobber | Use-Ternary { [System.IO.FileMode]::CreateNew } { [System.IO.FileMode]::Create } }

        $Template = {
            "**********************"
            "Windows PowerShell log start"
            "Version: {0} ({1})" -f $PSVersionTable.PSVersion, $PSVersionTable.PSEdition
            "Start time: {0:$( $Format[0] )}" -f $DateTime.($Format[1]).Invoke()
            "**********************"
        }
    }

    ## PROCESS ################################################################
    process {
        $Process = ($PSCmdlet.ParameterSetName -cmatch "^LiteralPath") | Use-Ternary { Resolve-PoshPath -LiteralPath $LiteralPath } { Resolve-PoshPath -Path $Path }

        foreach ($Object in $Process) {
            try {
                if ($Object.Provider.Name -ne "FileSystem") {
                    New-ArgumentException "The argument specified must resolve to a valid path on the FileSystem provider." -Throw
                }

                $FileInfo = [System.IO.FileInfo] $Object.ProviderPath

                if (-not ($Directory = $FileInfo.Extension | Use-Ternary { $FileInfo.Directory } { $FileInfo }).Exists) {
                    $null = [System.IO.Directory]::CreateDirectory($Directory)
                }

                if (-not $FileInfo.Extension) {
                    $FileInfo = [System.IO.FileInfo] ("PowerShell_log.{0}.{1:$( $Format[2] )}.txt" -f ([guid]::NewGuid() -isplit "-")[0], $DateTime.($Format[1]).Invoke())
                }

                Use-Object ($File = [System.IO.File]::Open($Directory.FullName + "\" + $FileInfo.Name, $FileMode)) {
                    if ($Append) {
                        $NewLine = [System.Text.Encoding]::UTF8.GetBytes([System.Environment]::NewLine)
                        $File.Write($NewLine, 0, $NewLine.Length)
                    }

                    foreach ($Line in $Template.Invoke()) {
                        $Bytes = [System.Text.Encoding]::UTF8.GetBytes($Line + [System.Environment]::NewLine)
                        $File.Write($Bytes, 0, $Bytes.Length)
                    }
                }

                $Global:PSLogDetails += @(@{ Path = $File.Name; Utc = $AsUtc })
                Write-Information -InformationAction Continue -MessageData ("Log started, output file is '{0}'" -f $File.Name) -InformationVariable null

                ## EXCEPTIONS #################################################
            } catch [System.Management.Automation.MethodInvocationException] {
                $PSCmdlet.WriteError(( New-MethodInvocationException -Exception $_.Exception.InnerException ))
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }

    ## END ####################################################################
    end {
        if (-not (Get-EventSubscriber | Where-Object SourceIdentifier -CMatch "^PSLog") -and $PSLogDetails) {
            $Global:PSLogInformation = [System.Collections.ObjectModel.ObservableCollection[System.Management.Automation.InformationRecord]]::new()
            $Global:PSLogWarning = [System.Collections.ObjectModel.ObservableCollection[System.Management.Automation.WarningRecord]]::new()
            $Global:PSLogError = [System.Collections.ObjectModel.ObservableCollection[System.Management.Automation.ErrorRecord]]::new()

            $Action = { Write-PoshLog -PSEventArgs $Event }

            $null = Register-ObjectEvent -EventName CollectionChanged -InputObject $PSLogInformation -SourceIdentifier PSLogInformation -Action $Action
            $null = Register-ObjectEvent -EventName CollectionChanged -InputObject $PSLogWarning -SourceIdentifier PSLogWarning -Action $Action
            $null = Register-ObjectEvent -EventName CollectionChanged -InputObject $PSLogError -SourceIdentifier PSLogError -Action $Action

            $Global:PSDefaultParameterValues["Write-Information:InformationVariable"] = "+PSLogInformation"
            $Global:PSDefaultParameterValues["Write-Warning:WarningVariable"] = "+PSLogWarning"
            $Global:PSDefaultParameterValues["Write-Error:ErrorVariable"] = "+PSLogError"
        }

        if ($PassThru) {
            Write-Output (, $PSLogDetails.Path)
        }
    }
}
#endregion
#region: ./src/Public/Stop-PoshLog.ps1
function Stop-PoshLog {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [CmdletBinding()]
    [OutputType([void])]

    ## PARAMETERS #############################################################
    param ()

    ## BEGIN ##################################################################
    begin {
        if (-not $PSLogDetails) {
            $PSCmdlet.ThrowTerminatingError(( New-PSInvalidOperationException -Message "An error occurred stopping the log: The host is not currently logging." ))
        }

        if ($Events = Get-EventSubscriber | Where-Object SourceIdentifier -CMatch "^PSLog") {
            $Events | Unregister-Event

            $Global:PSDefaultParameterValues.Remove("Write-Information:InformationVariable")
            $Global:PSDefaultParameterValues.Remove("Write-Warning:WarningVariable")
            $Global:PSDefaultParameterValues.Remove("Write-Error:ErrorVariable")
        }

        $DateTime = [datetime]::Now

        $Template = {
            "**********************"
            "Windows PowerShell log end"
            "End time: {0:$( $Format[0] )}" -f $DateTime.($Format[1]).Invoke()
            "**********************"
        }
    }

    ## PROCESS ################################################################
    process {
        foreach ($PSLog in $PSLogDetails) {
            try {
                $Format = $PSLog.Utc | Use-Ternary { "yyyy\-MM\-dd HH:mm:ss\Z", "ToUniversalTime" } { "yyyy\-MM\-dd HH:mm:ss", "ToLocalTime" }

                Use-Object ($File = [System.IO.File]::AppendText($PSLog.Path)) {
                    foreach ($Line in $Template.Invoke()) {
                        $File.WriteLine($Line)
                    }
                }

                Write-Information -InformationAction Continue -MessageData ("Log stopped, output file is '{0}'" -f $PSLog.Path) -InformationVariable null

                ## EXCEPTIONS #################################################
            } catch [System.Management.Automation.MethodInvocationException] {
                $PSCmdlet.WriteError(( New-MethodInvocationException -Exception $_.Exception.InnerException ))
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }

    ## END ####################################################################
    end {
        Remove-Variable -Name PSLog* -Scope Global
    }
}
#endregion
#region: ./src/Public/Use-ErrorCoalescing.ps1
function Use-ErrorCoalescing {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [CmdletBinding()]
    [Alias("?!")]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Mandatory,
            ValueFromPipeline
        )]
        [scriptblock]
        $InputObject,

        [Parameter(
            Position = 0
        )]
        [AllowNull()]
        [object]
        $IfError
    )

    ## PROCESS ################################################################
    process {
        # wrapping in an array to handle $null as input
        foreach ($Object in @($InputObject)) {
            try {
                . $Object
            } catch {
                if ($IfError -is [scriptblock]) {
                    . $IfError
                } else {
                    Write-Output (, $IfError)
                }
            }
        }
    }
}
#endregion
#region: ./src/Public/Use-NullCoalescing.ps1
function Use-NullCoalescing {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [CmdletBinding()]
    [Alias("??")]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Mandatory,
            ValueFromPipeline
        )]
        [AllowNull()]
        [AllowEmptyString()]
        [AllowEmptyCollection()]
        [object]
        $InputObject,

        [Parameter(
            Position = 0,
            Mandatory
        )]
        [object]
        $IfNull
    )

    ## END ####################################################################
    end {
        if (-not ($InputObject = $input)) {
            $InputObject = , $null
        }

        foreach ($Object in $InputObject) {
            try {
                if (($null -eq $Object) -and ($IfNull -is [scriptblock])) {
                    . $IfNull
                } elseif ($null -eq $Object) {
                    Write-Output (, $IfNull)
                } else {
                    Write-Output (, $Object)
                }
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }
}
#endregion
#region: ./src/Public/Use-Object.ps1
function Use-Object {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [CmdletBinding()]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [AllowEmptyString()]
        [AllowEmptyCollection()]
        [AllowNull()]
        [object]
        $InputObject,

        [Parameter(
            Position = 1,
            Mandatory
        )]
        [scriptblock]
        $ScriptBlock
    )

    ## PROCESS ################################################################
    process {
        try {
            . $ScriptBlock
        } catch {
            throw $_
        } finally {
            foreach ($Object in $InputObject) {
                if ($Object -is [System.IDisposable]) {
                    $Object.Dispose()
                } elseif ([System.Runtime.InteropServices.Marshal]::IsComObject($Object)) {
                    $null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($Object)
                }
            }
        }
    }
}
#endregion
#region: ./src/Public/Use-Ternary.ps1
function Use-Ternary {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [CmdletBinding()]
    [Alias("?:")]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Mandatory,
            ValueFromPipeline
        )]
        [AllowNull()]
        [AllowEmptyString()]
        [AllowEmptyCollection()]
        [object]
        $InputObject,

        [Parameter(
            Position = 0,
            Mandatory
        )]
        [object]
        $IfTrue,

        [Parameter(
            Position = 1,
            Mandatory
        )]
        [object]
        $IfFalse
    )

    ## PROCESS ################################################################
    process {
        # wrapping in an array to handle $null as input
        foreach ($Object in @($InputObject)) {
            try {
                if ($Object -and ($IfTrue -is [scriptblock])) {
                    . $IfTrue
                } elseif ($Object) {
                    Write-Output (, $IfTrue)
                } elseif ($IfFalse -is [scriptblock]) {
                    . $IfFalse
                } else {
                    Write-Output (, $IfFalse)
                }
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }
}
#endregion
#region: ./src/Public/Write-PoshLog.ps1
function Write-PoshLog {
    # Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
    [CmdletBinding(
        DefaultParameterSetName = "Type"
    )]
    [OutputType([void])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Mandatory,
            DontShow,
            ParameterSetName = "PSEventArgs"
        )]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSEventArgs]
        $PSEventArgs,

        [Parameter(
            ParameterSetName = "Type"
        )]
        [ValidateSet("Log", "Information", "Warning", "Error")]
        [string]
        $Type = "Log",

        [Parameter(
            Position = 0,
            Mandatory,
            ParameterSetName = "Type"
        )]
        [string]
        $Message
    )

    ## BEGIN ##################################################################
    begin {
        if (-not $PSLogDetails) {
            $PSCmdlet.ThrowTerminatingError(( New-PSInvalidOperationException -Message "An error occurred writing the log: The host is not currently logging." ))
        }

        $TypeMap = @{
            'Log'         = "LOG"
            'Information' = "INFO"
            'Warning'     = "WARN"
            'Error'       = "ERROR"
        }

        $Template = {
            "{0:$( $Format[0] )}`t[{1}] `t{2}" -f $DateTime.($Format[1]).Invoke(), $TypeMap.$Type, $Message
        }

        if ($PSEventArgs) {
            $DateTime = $PSEventArgs.TimeGenerated

            switch ($PSEventArgs.SourceIdentifier) {
                "PSLogInformation" {
                    $Type = "Information"
                    $Message = $PSEventArgs.SourceEventArgs.NewItems.MessageData
                }
                "PSLogWarning" {
                    $Type = "Warning"
                    $Message = $PSEventArgs.SourceEventArgs.NewItems.Message
                }
                "PSLogError" {
                    $Type = "Error"
                    $Message = $PSEventArgs.SourceEventArgs.NewItems.Exception.Message
                }
            }
        } else {
            $DateTime = [datetime]::Now
        }
    }

    ## PROCESS ################################################################
    process {
        foreach ($PSLog in $PSLogDetails) {
            try {
                $Format = $PSLog.Utc | Use-Ternary { "yyyy\-MM\-dd HH:mm:ss\Z", "ToUniversalTime" } { "yyyy\-MM\-dd HH:mm:ss", "ToLocalTime" }

                Use-Object ($File = [System.IO.File]::AppendText($PSLog.Path)) {
                    $File.WriteLine($Template.Invoke()[0])
                }

                ## EXCEPTIONS #################################################
            } catch [System.Management.Automation.MethodInvocationException] {
                $PSCmdlet.WriteError(( New-MethodInvocationException -Exception $_.Exception.InnerException ))
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }

    ## END ####################################################################
    end {
    }
}
#endregion
#endregion