PSUtil.psm1

$script:ModuleRoot = $PSScriptRoot
$script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\PSUtil.psd1").ModuleVersion

# Detect whether at some level dotsourcing was enforced
$script:doDotSource = Get-PSFConfigValue -FullName PSUtil.Import.DoDotSource -Fallback $false
if ($PSUtil_dotsourcemodule) { $script:doDotSource = $true }

<#
Note on Resolve-Path:
All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator.
This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS.
Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist.
This is important when testing for paths.
#>


# Detect whether at some level loading individual module files, rather than the compiled module was enforced
$importIndividualFiles = Get-PSFConfigValue -FullName PSUtil.Import.IndividualFiles -Fallback $false
if ($PSUtil_importIndividualFiles) { $importIndividualFiles = $true }
if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true }
if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true }
    
function Import-ModuleFile
{
    <#
        .SYNOPSIS
            Loads files into the module on module import.
         
        .DESCRIPTION
            This helper function is used during module initialization.
            It should always be dotsourced itself, in order to proper function.
             
            This provides a central location to react to files being imported, if later desired
         
        .PARAMETER Path
            The path to the file to load
         
        .EXAMPLE
            PS C:\> . Import-ModuleFile -File $function.FullName
     
            Imports the file stored in $function according to import policy
    #>

    [CmdletBinding()]
    Param (
        [string]
        $Path
    )
    
    $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath
    if ($doDotSource) { . $resolvedPath }
    else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) }
}

#region Load individual files
if ($importIndividualFiles)
{
    # Execute Preimport actions
    . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\preimport.ps1"
    
    # Import all internal functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
    {
        . Import-ModuleFile -Path $function.FullName
    }
    
    # Import all public functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
    {
        . Import-ModuleFile -Path $function.FullName
    }
    
    # Execute Postimport actions
    . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\postimport.ps1"
    
    # End it here, do not load compiled code below
    return
}
#endregion Load individual files

#region Load compiled code
Register-PSFConfigValidation -Name "PSUBrowseHistoryOptions" -ScriptBlock {
    param (
        $Value
    )
    
    $result = New-Object PSObject -Property @{
        Message  = ""
        Value    = $null
        Success  = $false
    }
    
    try { [PSUtil.Configuration.HistoryOption]$option = $Value }
    catch
    {
        $result.Message = "Could not convert $Value into a valid help option. Please specify either of these: Session | Global"
        return $result
    }
    
    $result.Success = $true
    $result.Value = $option
    
    return $result
}

Register-PSFConfigValidation -Name "PSUConsoleWidth" -ScriptBlock {
    param (
        $Value
    )
    
    $result = New-Object PSObject -Property @{
        Message  = ""
        Value    = $null
        Success  = $false
    }
    
    try { [int]$option = $Value }
    catch
    {
        $result.Message = "Could not convert $Value into a valid integer | $_"
        return $result
    }
    
    if ($option -le 0)
    {
        $result.Message = "Cannot specify a window width of 0 or less"
        return $result
    }
    
    if ($option -gt $Host.UI.RawUI.MaxWindowSize.Width)
    {
        $result.Message = "Cannot specify a window width larger than the maximum width: $option / $($Host.UI.RawUI.MaxWindowSize.Width)"
        return $result
    }
    
    $result.Success = $true
    $result.Value = $option
    
    return $result
}

Register-PSFConfigValidation -Name "PSUGetHelpOptions" -ScriptBlock {
    param (
        $Value
    )
    
    $result = New-Object PSObject -Property @{
        Message = ""
        Value = $null
        Success = $false
    }
    
    try { [PSUtil.Configuration.HelpOption]$option = $Value }
    catch
    {
        $result.Message = "Could not convert $Value into a valid help option. Please specify either of these: Short | Detailed | Examples | Full | Window | Online"
        return $result
    }
    
    $result.Success = $true
    $result.Value = $option
    
    return $result
}

# Configure the current window width
Set-PSFConfig -Module PSUtil -Name 'Console.Width' -Value ($Host.UI.RawUI.WindowSize.Width) -Initialize -Validation "PSUConsoleWidth" -Handler { Set-PSUShell -WindowWidth $args[0] } -Description "The width of the current console"

# Buffer Length
Set-PSFConfig -Module PSUtil -Name 'Console.Buffer' -Value ($Host.UI.RawUI.BufferSize.Height) -Initialize -Validation "integerpositive" -Handler { Set-PSUShell -BufferLength $args[0] } -Description "The length of the console screen history"

# Foreground Color
Set-PSFConfig -Module PSUtil -Name 'Console.ForegroundColor' -Value ($Host.ui.rawui.ForegroundColor) -Initialize -Validation "consolecolor" -Handler { Set-PSUShell -ForegroundColor $args[0] } -Description "The foreground color used in the PowerShell console"

# Background Color
Set-PSFConfig -Module PSUtil -Name 'Console.BackgroundColor' -Value ($Host.ui.rawui.BackgroundColor) -Initialize -Validation "consolecolor" -Handler { Set-PSUShell -BackgroundColor $args[0] } -Description "The background color used in the PowerShell console"

# Window Title
Set-PSFConfig -Module PSUtil -Name 'Console.WindowTitle' -Value ($Host.ui.rawui.Windowtitle) -Initialize -Validation "string" -Handler { Set-PSUShell -WindowTitle $args[0] } -Description "The background color used in the PowerShell console"

Set-PSFConfig -Module PSUtil -Name 'Import.Aliases.Grep' -Value $true -Initialize -Validation "bool" -Handler { } -Description "Whether the module will on import create an alias named 'grep' for Select-String"
Set-PSFConfig -Module PSUtil -Name 'Import.Keybindings' -Value $true -Initialize -Validation "bool" -Handler { } -Description "Whether the module will on import register keybindings in PSReadline"
Set-PSFConfig -Module PSUtil -Name 'Import.Alias.SystemOverride' -Value $false -Initialize -Validation "bool" -Description "Whether the module will on import pverwrite some default aliases with custom versions. Affects 'select' and 'gm'"

Set-PSFConfig -Module 'PSUtil' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging."
Set-PSFConfig -Module 'PSUtil' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments."

Set-PSFConfig -Module 'PSUtil' -Name 'Keybinding.GetHelp' -Value "F1" -Initialize -Description "The key chord(s) the open help functionality is bound to. Will provide help for the currently typed command."
Set-PSFConfig -Module 'PSUtil' -Name 'Keybinding.ExpandAlias' -Value "Alt+Q" -Initialize -Description "The key chord(s) the alias expansion functionality is bound to. Will expand all aliases in the current input."
Set-PSFConfig -Module 'PSUtil' -Name 'Keybinding.CopyAll' -Value "Ctrl+Shift+c" -Initialize -Description "The key chord(s) the copy all functionality is bound to. Will copy all lines of the current input to the clipboard."
Set-PSFConfig -Module 'PSUtil' -Name 'Keybinding.BrowseHistory' -Value "F7" -Initialize -Description "The key chord(s) the browse history functionality is bound to. Will open a gridview and allow you to pick from your input history, then insert the chosen line(s) as your current input."
Set-PSFConfig -Module 'PSUtil' -Name 'Keybinding.SendToHistory' -Value "Alt+w" -Initialize -Description "The key chord(s) the send to history functionality is bound to. Your current input will be sent to history without actually executing it. Access it by using the Arrow-UP key."

$defaultHelpPreference = [PSUtil.Configuration.HelpOption]::Window
if (-not (Test-PSFPowerShell -OperatingSystem Windows))
{
    $defaultHelpPreference = [PSUtil.Configuration.HelpOption]::Detailed
}
Set-PSFConfig -Module PSUtil -Name 'Help.Preference' -Value $defaultHelpPreference -Initialize -Validation "PSUGetHelpOptions" -Handler { } -Description "The way in which help is shown when pressing the 'F1' key. Can be any of the following options: Short | Detailed | Examples | Full | Window | Online"
Set-PSFConfig -Module PSUtil -Name 'History.Preference' -Value ([PSUtil.Configuration.HistoryOption]::Session) -Initialize -Validation "PSUBrowseHistoryOptions" -Handler { } -Description "Where the system will retrieve input history when pressing the 'F7' key. Can be any of the following options: Session | Global. Session includes history since the process was started, Global will try to look up the history from PSReadline log-file instead"
Set-PSFConfig -Module PSUtil -Name 'History.Limit' -Value (-1) -Initialize -Validation 'integer' -Handler { } -Description "The maximum number of history entries to show when pressing the 'F7' key. Negative numbers disable limit"
Set-PSFConfig -Module PSUtil -Name 'Expand.DefaultProperties' -Value "Definition", "Guid", "DisinguishedName", "FullName", "Name", "Length" -Initialize -Validation stringarray -Description "The properties Expand-PSUObject (exp) picks from by default"

Set-PSFConfig -Module PSUtil -Name 'Path.Temp' -Value $([System.IO.Path]::GetTempPath()) -Initialize -Validation "string" -Handler { } -Description "The path to move to when calling Invoke-PSUTemp (temp)"
Set-PSFConfig -Module PSUtil -Name 'Path.BackupStepsDefault' -Value 1 -Initialize -Validation "integer" -Description "The number of levels you stup up when calling Backup-PSULocation (bu) without parameter"


function Import-PSUAlias
{
    <#
        .SYNOPSIS
            Internal command to set aliases in a user-controlled fashion.
         
        .DESCRIPTION
            Internal command to set aliases in a user-controlled fashion.
            - Can be blocked by setting a config "PSUtil.Import.Aliases.$Name" to $false.
            - Will not overwrite existing aliases
         
        .PARAMETER Name
            Name of the alias to set.
         
        .PARAMETER Command
            Name of the command to alias.
         
        .EXAMPLE
            PS C:\> Import-PSUAlias -Name grep -Command Select-String
     
            Sets the alias grep for the command Select-String
    #>

    
    [CmdletBinding()]
    Param (
        $Name,
        
        $Command
    )
    
    if (((-not (Test-Path alias:$name)) -or ($Name -eq "Select") -or ($Name -eq "gm")) -and (Get-PSFConfigValue -FullName PSUtil.Import.Aliases.$name -Fallback $true))
    {
        New-Alias -Name $Name -Value $Command -Force -Scope Global
    }
}

function Backup-PSULocation
{
<#
    .SYNOPSIS
        Sets the location n number of levels up.
     
    .DESCRIPTION
        You no longer have to cd ..\..\..\..\ to move back four levels. You can now
        just type bu 4
     
    .PARAMETER Levels
        Number of levels to move back.
     
    .EXAMPLE
        PS C:\Users\dlbm3\source\pullrequests\somePR\vsteam> bu 4
         
        PS C:\Users\dlbm3>
     
    .NOTES
        Author: Donovan Brown
        Source: http://donovanbrown.com/post/Why-cd-when-you-can-just-backup
     
        Thank you for sharing and granting permission to use this convenience :)
#>

    [CmdletBinding()]
    param (
        [int]
        $Levels = (Get-PSFConfigValue -FullName 'PSUtil.Path.BackupStepsDefault' -Fallback 1)
    )
    
    Set-Location -Path (,".." * $Levels | Join-PSUString -With ([System.IO.Path]::DirectorySeparatorChar))
}

Import-PSUAlias -Name "bu" -Command "Backup-PSULocation"

function Invoke-PSUDesktop
{
    <#
        .SYNOPSIS
            Function that sets the current console path to the user desktop.
         
        .DESCRIPTION
            Function that sets the current console path to the user desktop.
            Uses the current user's desktop by default, but can be set to the desktop of any locally available profile.
         
        .PARAMETER User
            Alias: u
            Choose which user's desktop path to move to. Must be available as a local profile for things to work out.
     
        .PARAMETER Get
            Alias: g
            Returns the path, rather than changing the location
         
        .EXAMPLE
            PS C:\> Desktop
     
            Sets the current location to the desktop path of the current user.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "")]
    [CmdletBinding()]
    Param (
        [Parameter(Position = 0)]
        [Alias('u')]
        [string]
        $User = $env:USERNAME,
        
        [Alias('g')]
        [switch]
        $Get
    )
    
    # Default Path for current user
    $Path = "$env:SystemDrive\Users\$User\Desktop"
    
    if (-not (Test-Path $Path))
    {
        Stop-PSFFunction -Message "Path to Desktop not found: $Path" -Tag fail -Target $User -Category InvalidArgument
        return
    }
    
    if ($Get) { return $Path }
    else { Push-Location $Path }
}
Import-PSUAlias -Name "desktop" -Command "Invoke-PSUDesktop"

function Invoke-PSUExplorer
{
<#
    .SYNOPSIS
        Opens the windows explorer at the specified position.
     
    .DESCRIPTION
        Opens the windows explorer at the specified position.
     
    .PARAMETER Path
        Alias: FullName
        Default: (Get-Location).Path
        The folder to open in explorer. If a file was passed the containing folder will be opened instead.
     
    .PARAMETER Module
        The module, the base folder of which should be opened.
     
    .PARAMETER Duplicates
        Setting this switch will cause the function to open the same folder multiple times, if it was passed multiple times.
        By default, the function will not open the same folder multiple times (a dir of a directory with multiple files would otherwise cause multiple open windows).
     
    .PARAMETER EnableException
        This parameters disables user-friendly warnings and enables the throwing of exceptions.
        This is less user friendly, but allows catching exceptions in calling scripts.
     
    .EXAMPLE
        PS C:\> dir | Explorer
         
        Opens each folder in the current directory in a separate explorer Window.
#>

    [CmdletBinding()]
    Param (
        [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('FullName')]
        [string[]]
        $Path = (Get-Location).ProviderPath,
        
        [System.Management.Automation.PSModuleInfo[]]
        $Module,
        
        [switch]
        $Duplicates,
        
        [switch]
        $EnableException
    )
    
    Begin {
        # Contains previously opened folders
        $List = @()
    }
    Process {
        foreach ($item in $Path)
        {
            try { $resolvedPaths = Resolve-PSFPath -Path $item -Provider FileSystem }
            catch { Stop-PSFFunction -Message "Path not found: $item" -EnableException $EnableException -ErrorRecord $_ -Continue -Tag input -Target $item }
            
            foreach ($resolvedPath in $resolvedPaths)
            {
                
                $object = Get-Item $resolvedPath
                
                if ($object.PSIsContainer) { $finalPath = $object.FullName }
                else { $finalPath = $object.Directory.FullName }
                
                # If it was already opened once, skip it unless duplicates are enabled
                if ((-not $Duplicates) -and ($List -contains $finalPath))
                {
                    Write-PSFMessage -Level Verbose -Message "Skipping folder since it already was opened once: $finalPath" -Target $item -Tag skip
                    continue
                }
                
                Write-PSFMessage -Level Verbose -Message "Opening explorer at: $finalPath"
                explorer.exe $finalPath
                $List += $finalPath
            }
        }
        
        foreach ($item in $Module)
        {
            if ((-not $Duplicates) -and ($List -contains $item.ModuleBase))
            {
                Write-PSFMessage -Level Verbose -Message "Skipping folder since it already was opened once: $($item.ModuleBase)" -Target $item -Tag skip
                continue
            }
            Write-PSFMessage -Level Verbose -Message "Opening explorer at: $($item.ModuleBase)"
            explorer.exe $item.ModuleBase
            $List += $item.ModuleBase
        }
    }
}
Import-PSUAlias -Name "explorer" -Command "Invoke-PSUExplorer"

function Invoke-PSUTemp
{
    <#
        .SYNOPSIS
            Moves the current location to a temp directory.
         
        .DESCRIPTION
            Moves the current location to a temp directory.
     
            The path returned can be set by configuring the 'psutil.path.temp' configuration. E.g.:
            Set-PSFConfig "psutil.path.temp" "D:\temp\_Dump"
     
            If this configuration is not set, it will check the following locations and return the first one found:
            C:\Temp
            D:\Temp
            E:\Temp
            C:\Service
            $env:temp
         
        .PARAMETER Get
            Alias: g
            Rather than move to the directory, return its path.
         
        .EXAMPLE
            PS C:\> Invoke-PSUTemp
     
            Moves to the temporary directory.
    #>

    [CmdletBinding()]
    Param (
        [Alias('g')]
        [switch]
        $Get
    )
    
    if ($Get) { Get-PSFConfigValue -FullName 'PSUtil.Path.Temp' -Fallback $env:TEMP }
    else { Push-Location -Path (Get-PSFConfigValue -FullName 'PSUtil.Path.Temp' -Fallback $env:TEMP) }
}
Import-PSUAlias -Name "temp" -Command "Invoke-PSUTemp"

function New-PSUDirectory
{
<#
    .SYNOPSIS
        Creates a folder and moves the current path to it.
     
    .DESCRIPTION
        Creates a folder and moves the current path to it.
     
    .PARAMETER Path
        Name of the folder to create and move to.
     
    .EXAMPLE
        PS C:\> mcd Test
     
        creates folder C:\Test, then moves the current location to it.
     
    .NOTES
        Author: Donovan Brown
        Source: http://donovanbrown.com/post/How-to-create-and-navigate-a-directory-with-a-single-command
     
        Thank you for sharing and granting permission to use this convenience :)
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        $Path
    )
    
    New-Item -Path $Path -ItemType Directory
    
    Set-Location -Path $Path
}

Import-PSUAlias -Name "mcd" -Command "New-PSUDirectory"

function Set-PSUDrive
{
<#
    .SYNOPSIS
        Creates a new psdrive, and moves location to it.
     
    .DESCRIPTION
        Will create a PSDrive, by default in the current path.
        This allows swiftly reducing path length.
        Then it will immediately change location to the new drive.
     
    .PARAMETER Name
        What to name the new PSDrive?
     
    .PARAMETER Root
        Default: .
        The root of the new drive.
     
    .EXAMPLE
        PS C:\> set-as pr
     
        Sets the current path as drive "pr" and sets it as the current location.
     
    .NOTES
        Author: Donovan Brown
        Source: http://donovanbrown.com/post/Shorten-your-PowerShell-directory-path
     
        Thank you for sharing and granting permission to use this convenience :)
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        $Name,
        
        [string]
        $Root = "."
    )
    
    $path = Resolve-Path $Root
    $null = New-PSDrive -PSProvider $path.Provider -Name $Name -Root $Root -Scope Global
    Set-Location -LiteralPath "$($Name):"
}

Import-PSUAlias -Name "set-as" -Command "Set-PSUDrive"

function Convert-PSUObject
{
<#
    .SYNOPSIS
        Converts objects from one data-type/-format to another.
     
    .DESCRIPTION
        Converts objects from one data-type/-format to another.
        For example can this be used to convert numbers from binary to hex.
     
        This function can be dynamically extended by registering conversion paths.
        Use Register-PSUObjectConversion to set up such a type conversion.
     
    .PARAMETER InputObject
        The object(s) to convert.
     
    .PARAMETER From
        The type/format that is assumed to be the input type.
     
    .PARAMETER To
        The type/format that the input is attempted to convert to.
     
    .PARAMETER EnableException
        Replaces user friendly yellow warnings with bloody red exceptions of doom!
        Use this if you want the function to throw terminating errors you want to catch.
     
    .EXAMPLE
        PS C:\> 100..110 | convert IntDec IntHex
     
        Converts the numbers 100 through 110 from decimal to hexadecimal.
#>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $InputObject,
        
        [Parameter(Mandatory = $true, Position = 0)]
        [string]
        $From,
        
        [Parameter(Mandatory = $true, Position = 1)]
        [string]
        $To,
        
        [switch]
        $EnableException
    )
    
    begin
    {
        if (-not ([PSUtil.Object.ObjectHost]::Conversions.ContainsKey("$($From):$($To)")))
        {
            Stop-PSFFunction -Message "No conversion path configured for $From --> $To" -EnableException $EnableException -Category NotImplemented -Tag 'fail', 'input', 'convert'
            return
        }
        
        $scriptBlock = [PSUtil.Object.ObjectHost]::Conversions["$($From):$($To)"].Script
    }
    process
    {
        if (Test-PSFFunctionInterrupt) { return }
        
        foreach ($item in $InputObject)
        {
            [PSFramework.Utility.UtilityHost]::ImportScriptBlock($scriptBlock)
            try { $scriptBlock.Invoke($item) }
            catch
            {
                Stop-PSFFunction -Message "Failed to convert $item from $From to $To" -EnableException $EnableException -ErrorRecord $_ -Target $item -Tag 'fail','convert','item' -Continue
            }
        }
    }
}
Import-PSUAlias -Name convert -Command Convert-PSUObject

function Expand-PSUObject
{
    <#
        .SYNOPSIS
            A comfortable replacement for Select-Object -ExpandProperty.
         
        .DESCRIPTION
            A comfortable replacement for Select-Object -ExpandProperty.
            Allows extracting properties with less typing and more flexibility:
     
            Preferred Properties:
            By defining a list of property-names in $DefaultExpandedProperties the user can determine his own list of preferred properties to expand.
            This allows using this command without specifying a property at all.
            It will then check the first object for the property to use (starting from the first element of the list until it finds an exact case-insensitive match).
     
            Defined Property:
            The user can specify the exact property to extract. This is the same behavior as Select-Object -ExpandProperty, with less typing (dir | exp length).
     
            Like / Match comparison:
            Specifying either like or match allows extracting any number of matching properties from each object.
            Note that this is a somewhat more CPU-expensive operation (which shouldn't matter unless with gargantuan numbers of objects).
         
        .PARAMETER Name
            ParSet: Equals, Like, Match
            The name of the Property to expand.
         
        .PARAMETER Like
            ParSet: Like
            Expands all properties that match the -Name parameter using -like comparison.
         
        .PARAMETER Match
            ParSet: Match
            Expands all properties that match the -Name parameter using -match comparison.
         
        .PARAMETER InputObject
            The objects whose properties are to be expanded.
         
        .EXAMPLE
            PS C:\> dir | exp
     
            Expands the property whose name is the first on the defaults list ($DefaultExpandedProperties).
            By default, FullName would be expanded.
     
        .EXAMPLE
            PS C:\> dir | exp length
     
            Expands the length property of all objects returned by dir. Simply ignores those that do not have the property (folders).
     
        .EXAMPLE
            PS C:\> dir | exp name -match
     
            Expands all properties from all objects returned by dir that match the string "name" ("PSChildName", "FullName", "Name", "BaseName" for directories)
    #>

    [CmdletBinding(DefaultParameterSetName = "Equals")]
    Param (
        [Parameter(Position = 0, ParameterSetName = "Equals")]
        [Parameter(Position = 0, ParameterSetName = "Like", Mandatory = $true)]
        [Parameter(Position = 0, ParameterSetName = "Match", Mandatory = $true)]
        [string]
        $Name,
        
        [Parameter(ParameterSetName = "Like", Mandatory = $true)]
        [switch]
        $Like,
        
        [Parameter(ParameterSetName = "Match", Mandatory = $true)]
        [switch]
        $Match,
        
        [Parameter(ValueFromPipeline = $true)]
        [object]
        $InputObject
    )
    
    Begin
    {
        Write-PSFMessage -Level Debug -Message "Expanding Objects" -Tag start
        $ParSet = $PSCmdlet.ParameterSetName
        Write-PSFMessage -Level InternalComment -Message "Active Parameterset: $ParSet | Bound Parameters: $($PSBoundParameters.Keys -join ", ")" -Tag start
        
        # Null the local scoped variable (So later checks for existence don't return super-scoped variables)
        $n9ZPiBh8CI = $null
        [bool]$____found = $false
        
        # If a property was specified, set it and return it
        if (Test-PSFParameterBinding -ParameterName "Name")
        {
            $n9ZPiBh8CI = $Name
            $____found = $true
        }
        $DefaultExpandedProperties = Get-PSFConfigValue -FullName 'PSutil.Expand.DefaultProperties'
    }
    
    process
    {
        :main foreach ($Object in $InputObject)
        {
            if ($null -eq $Object) { continue }
            
            switch ($ParSet)
            {
                #region Equals
                "Equals"
                {
                    # If we didn't ask for a property in specific, and we have something prepared for this type: Run it
                    if ((Test-PSFParameterBinding -ParameterName "Name" -Not) -and ([PSUtil.Object.ObjectHost]::ExpandedTypes[$Object.GetType().FullName]))
                    {
                        [PSUtil.Object.ObjectHost]::ExpandedTypes[$Object.GetType()].Invoke($Object)
                        continue main
                    }
                    
                    # If we already have determined the property to use, return it
                    if ($____found)
                    {
                        if ($null -ne $Object.$n9ZPiBh8CI) { $Object.$n9ZPiBh8CI }
                        continue main
                    }
                    
                    # Otherwise, search through defaults and try to match
                    foreach ($Def in $DefaultExpandedProperties)
                    {
                        if (Get-Member -InputObject $Object -MemberType 'Properties' -Name $Def)
                        {
                            $n9ZPiBh8CI = $Def
                            $____found = $true
                            if ($null -ne $Object.$n9ZPiBh8CI) { $Object.$n9ZPiBh8CI }
                            
                            break
                        }
                    }
                    continue main
                }
                #endregion Equals
                
                #region Like
                "Like"
                {
                    # Return all properties whose name are similar
                    foreach ($prop in ($Object.PSObject.Properties | Where-Object Name -like $Name | Select-Object -ExpandProperty Name))
                    {
                        if ($null -ne $Object.$prop) { $Object.$prop }
                    }
                    continue
                }
                #endregion Like
                
                #region Match
                "Match"
                {
                    # Return all properties whose name match
                    foreach ($prop in ($Object.PSObject.Properties | Where-Object Name -Match $Name | Select-Object -ExpandProperty Name))
                    {
                        if ($null -ne $Object.$prop) { $Object.$prop }
                    }
                    continue main
                }
                #endregion Match
            }
        }
    }
    
    End
    {
        Write-PSFMessage -Level Debug -Message "Expanding Objects" -Tag end
    }
}
Import-PSUAlias -Name "exp" -Command "Expand-PSUObject"

function Register-PSUObjectConversion
{
<#
    .SYNOPSIS
        Registers an object conversion for Convert-PSUObject.
     
    .DESCRIPTION
        This command can be used to register an object conversion for Convert-PSUObject, allowing the user to extend the conversion utility as desired.
     
    .PARAMETER From
        The input type. Using a suitable shorthand is recommended ("int" rather than "System.Int32", etc.).
     
    .PARAMETER To
        The conversion target type. Using a suitable shorthand is recommended ("int" rather than "System.Int32", etc.).
     
    .PARAMETER ScriptBlock
        The scriptblock that will be invoked to convert.
        Receives a single argument: The input object to convert.
     
    .EXAMPLE
        PS C:\> Register-PSUObjectConversion -From 'dec' -To 'oct' -ScriptBlock $ScriptBlock
     
        Registers a conversion that is supposed to convert a decimal into an octal number.
#>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [string]
        $From,
        
        [Parameter(Mandatory = $true)]
        [string]
        $To,
        
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.ScriptBlock]
        $ScriptBlock
    )
    
    process
    {
        $conversion = New-Object PSUtil.Object.ObjectConversionMapping -Property @{
            From   = $From
            To     = $To
            Script = $ScriptBlock
        }
        
        [PSUtil.Object.ObjectHost]::Conversions["$($From):$($To)"] = $conversion
    }
}

function Register-PSUObjectExpansion
{
<#
    .SYNOPSIS
        Registers a custom scriptblock for a type when processed by Expand-PSUObject.
     
    .DESCRIPTION
        Registers a custom scriptblock for a type when processed by Expand-PSUObject.
     
        Expand-PSUObject enables accelerated object expansion,
        by shortening the "Select-Object -ExpandProperty" call to "exp".
        It further has a list of default properties to expand,
        but it also allows implementing custom expansion rules, based on input type.
         
        This commands sets up these custom expansion rules.
        Define a scriptblock, it receives a single parameter - the input object to expand.
        The scriptblock is then responsible for expanding it and producing the desired output.
     
    .PARAMETER TypeName
        The name of the type to custom-expand.
     
    .PARAMETER ScriptBlock
        The scriptblock performing the expansion.
     
    .EXAMPLE
        PS C:\> Register-PSUObjectExpansion -TypeName 'MyModule.MyClass' -ScriptBlock $ScriptBlock
     
        Sets up a custom expansion rule for the 'MyModule.MyClass' class.
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]
        $TypeName,
        
        [Parameter(Mandatory = $true, Position = 1)]
        [scriptblock]
        $ScriptBlock
    )
    
    process
    {
        [PSUtil.Object.ObjectHost]::ExpandedTypes[$TypeName] = $ScriptBlock
    }
}

function Select-PSUObjectSample
{
    <#
        .SYNOPSIS
            Used to only pick a sample from the objects passed to the function.
         
        .DESCRIPTION
            Used to only pick a sample from the objects passed to the function.
         
        .PARAMETER InputObject
            The objects to pick a sample from.
         
        .PARAMETER Skip
            How many objects to skip.
         
        .PARAMETER Number
            How many objects to pick
            Use a negative number to pick the last X items instead.
         
        .EXAMPLE
            PS C:\> Get-ChildItem | Select-PSUObjectSample -Skip 1 -Number 3
     
            Scans the current directory, skips the first returned object, then passes through the next three objects and skips the rest.
         
        .EXAMPLE
            PS C:\> dir | s 3 1
     
            Same as the previous example, only this time using aliases and positional binding.
            Scans the current directory, skips the first returned object, then passes through the next three objects and skips the rest.
    #>

    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        $InputObject,
        
        [Parameter(Position = 1)]
        [int]
        $Skip = 0,
        
        [Parameter(Position = 0)]
        [int]
        $Number = 1
    )
    
    Begin
    {
        $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Select-Object', [System.Management.Automation.CommandTypes]::Cmdlet)
        $splat = @{ }
        if ($Skip -gt 0) { $splat["Skip"] = $Skip }
        if ($Number -ge 0) { $splat["First"] = $Number + 1 }
        else { $splat["Last"] = $Number * -1 }
        $scriptCmd = { & $wrappedCmd @splat }
        $steppablePipeline = $scriptCmd.GetSteppablePipeline()
        $steppablePipeline.Begin($true)
    }
    
    Process
    {
        foreach ($o in $InputObject)
        {
            $steppablePipeline.Process($o)
        }
    }
    
    End
    {
        $steppablePipeline.End()
    }
}
Import-PSUAlias -Name "s" -Command "Select-PSUObjectSample"

function Set-PSUObjectType
{
    <#
        .SYNOPSIS
            Tries to convert an object from one type of another.
         
        .DESCRIPTION
            Tries to convert an object from one type of another.
         
        .PARAMETER InputObject
            The objects to convert.
         
        .PARAMETER Type
            ParSet: Type
            The type to cast to.
         
        .PARAMETER TypeName
            ParSet: String
            The type to cast to.
         
        .PARAMETER EnableException
            Replaces user friendly yellow warnings with bloody red exceptions of doom!
            Use this if you want the function to throw terminating errors you want to catch.
         
        .EXAMPLE
            PS C:\> "01", "02", "03", "42" | Set-PSUObjectType "int"
             
            Tries to convert strings with numeric values into pure integers (hint: This will probably succeede).
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(DefaultParameterSetName = "String")]
    Param (
        [Parameter(ValueFromPipeline = $true)]
        $InputObject,
        
        [Parameter(ParameterSetName = "Type")]
        [Type]
        $Type,
        
        [Parameter(ParameterSetName = "String", Position = 0)]
        [String]
        $TypeName,
        
        [switch]
        $EnableException
    )
    
    Begin
    {
        Write-PSFMessage -Level Debug -Message "Casting Objects to another type" -Tag start
        $ParSet = $PSCmdlet.ParameterSetName
        Write-PSFMessage -Level InternalComment -Message "Active Parameterset: $ParSet | Bound Parameters: $($PSBoundParameters.Keys -join ", ")" -Tag start
        
        switch ($ParSet)
        {
            "Type" { $name = $Type.FullName }
            "String" { $name = $TypeName }
        }
    }
    Process
    {
        foreach ($object in $InputObject)
        {
            $temp = $null
            $temp = $object -as $name
            if ($temp) { $temp }
            else { Stop-PSFFunction -Message "Failed to convert '$object' to '$name'" -EnableException $EnableException -Category InvalidData -Tag fail, cast -Target $object -Continue }
        }
    }
    End
    {
        Write-PSFMessage -Level Debug -Message "Casting Objects to another type" -Tag end
    }
}
Import-PSUAlias -Name "cast" -Command "Set-PSUObjectType"

function Select-PSUFunctionCode
{
<#
    .SYNOPSIS
        Function that shows you the definition of a function and allows you to select lines to copy to your clipboard.
     
    .DESCRIPTION
        Function that shows you the definition of a function and allows you to select lines to copy to your clipboard.
        After running this command you will see a GridView pop up. Select as many lines of code as you would like and select
        ok to copy them to your clipboard.
     
    .PARAMETER Function
        A description of the Function parameter.
     
    .PARAMETER NoWait
        Shows function code in gridview and returns control without waiting for the window to close
     
    .PARAMETER PassThru
        Presents input command(s) in gridview, selected lines (if any) get returned as output
     
    .PARAMETER NoTrim
        If enabled, the white space will not be trimmed.
     
    .PARAMETER EnableException
        Replaces user friendly yellow warnings with bloody red exceptions of doom!
        Use this if you want the function to throw terminating errors you want to catch.
     
    .EXAMPLE
        PS C:\> Select-PSUFunctionCode -function 'Start-PSUTimer'
         
        This will open up the code for the function Start-PSUTimer in a GridView window.
     
    .EXAMPLE
        PS C:\> Get-Command timer | Select-PSUFunctionCode
         
        You can also pipe functions in.
     
    .NOTES
        Author: Andrew Pla
#>

    
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Function,
        
        [Alias('w')]
        [Parameter(ParameterSetName = 'NoWait')]
        [switch]
        $NoWait,
        
        [Alias('p')]
        [Parameter(ParameterSetName = 'PassThru')]
        [switch]
        $PassThru,
        
        [Alias('t')]
        [switch]
        $NoTrim,
        
        [switch]
        $EnableException
    )
    
    begin
    {
        Write-PSFMessage -Level InternalComment -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")" -Tag 'debug', 'start', 'param'
        
        if (Test-PSFPowerShell -PSMinVersion '6.0' -PSMaxVersion '6.9.9')
        {
            Stop-PSFFunction -Message "This command is not supported on PowerShell v6 due to lack of GUI elements!" -Category NotEnabled -Tag fail, ps6
            return
        }
        
        $FinalArray = [System.Collections.Arraylist]@()
    }
    process
    {
        if (Test-PSFFunctionInterrupt) { return }
        
        #region Process Items
        :main foreach ($item in $Function)
        {
            #region Resolve Command
            try { $command = Get-Command $item -ErrorAction Stop }
            catch { Stop-PSFFunction -Message "Failed to resolve command $item" -Tag fail -ErrorRecord $_ -Target $item -Continue -ContinueLabel main -EnableException $EnableException }
            
            switch ($command.CommandType)
            {
                'Alias'
                {
                    if ($command.ResolvedCommand.CommandType -eq "Function")
                    {
                        $functionText = $command.ResolvedCommand | Expand-PSUObject | Split-PSUString "`n"
                    }
                    else
                    {
                        Stop-PSFFunction -Message "$($command.ResolvedCommand.CommandType) not supported: The alias $item resolves to $($command.ResolvedCommand). Please supply a function or an alias for a function." -Tag fail -Target $item -Continue -ContinueLabel main -EnableException $EnableException
                    }
                }
                'Function'
                {
                    $functionText = $command | Expand-PSUObject | Split-PSUString "`n"
                }
                default
                {
                    Stop-PSFFunction -Message "$($command.CommandType) not supported: $item. Please supply a function or an alias for a function." -Tag fail -Target $item -Continue -ContinueLabel main -EnableException $EnableException
                }
            }
            #endregion Resolve Command
            
            #region Process Definition content
            $count = 1
            foreach ($line in $functionText)
            {
                $Object = [PSCustomObject]@{
                    LineNumber       = $Count
                    Text           = $line
                    Function       = $item
                }
                
                $count++
                
                if ($NoTrim)
                {
                    $null = $FinalArray.add($Object)
                }
                else
                {
                    if (-not ([string]::IsNullOrWhiteSpace($line)))
                    {
                        $null = $FinalArray.add($Object)
                    }
                }
            }
            #endregion Process Definition content
        }
        #endregion Process Items
    }
    
    end
    {
        if (Test-PSFFunctionInterrupt) { return }
        
        # This is the default behavior with no params
        if (-not ($NoWait -or $PassThru))
        {
            $data = $FinalArray | Out-GridView -PassThru -Title 'Select-PSUFunctionCode' | Expand-PSUObject text
            if ($data) { $data | Set-Clipboard }
        }
        
        if ($NoWait)
        {
            $FinalArray | Out-GridView -Title 'Select-PSUFunctionCode'
        }
        if ($PassThru)
        {
            $FinalArray | Out-GridView -PassThru -Title 'Select-PSUFunctionCode'
        }
    }
}

Import-PSUAlias -Name "inspect" -Command "Select-PSUFunctionCode"

function Set-PSUPrompt
{
<#
    .SYNOPSIS
        Applies one of the pre-defined prompts.
     
    .DESCRIPTION
        Applies one of the pre-defined prompts.
     
    .PARAMETER Prompt
        The prompt to apply
     
    .PARAMETER EnableException
        This parameters disables user-friendly warnings and enables the throwing of exceptions.
        This is less user friendly, but allows catching exceptions in calling scripts.
     
    .EXAMPLE
        PS C:\> Set-PSUPrompt -Prompt fred
     
        Applies the prompt fred uses.
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Prompt,
        
        [switch]
        $EnableException
    )
    
    process
    {
        if (-not (Test-Path "$script:ModuleRoot\internal\prompts\$Prompt.prompt.ps1"))
        {
            Stop-PSFFunction -Message "Failed to find prompt: $Prompt" -Target $Prompt -EnableException $EnableException -Category ObjectNotFound
            return
        }
        & "$script:ModuleRoot\internal\prompts\$Prompt.prompt.ps1"
    }
}

function Set-PSUShell
{
    <#
        .SYNOPSIS
            Command that sets various console properties
         
        .DESCRIPTION
            Command that sets various console properties.
         
        .PARAMETER WindowWidth
            The width of the console window.
            Not much of a change on windows 10, more of a chore on older console hosts.
         
        .PARAMETER BackgroundColor
            The background color to use.
            Is PSReadline aware.
         
        .PARAMETER ForegroundColor
            The foreground color to use.
            Is PSReadline aware.
         
        .PARAMETER BufferLength
            How lengthy a memory the console screen keeps.
            The size of the stuff cls clears.
         
        .PARAMETER WindowTitle
            The title the window should have.
         
        .PARAMETER EnableException
            Replaces user friendly yellow warnings with bloody red exceptions of doom!
            Use this if you want the function to throw terminating errors you want to catch.
         
        .EXAMPLE
            PS C:\> Set-PSUShell -WindowWidth 140 -WindowTitle "The Foo Shell" -ForegroundColor DarkGreen -BackgroundColor Black
     
            Sets the current shell to ...
            - 140 pixel width
            - have a title of "The Foo Shell"
            - Use a foreground color of DarkGreen for all output, default prompt color and comment color (PSReadline syntax detection remains unaffected)
            - Use a background color of Black
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    Param (
        [int]
        $WindowWidth,
        
        [System.ConsoleColor]
        $BackgroundColor,
        
        [System.ConsoleColor]
        $ForegroundColor,
        
        [int]
        $BufferLength,
        
        [string]
        $WindowTitle,
        
        [switch]
        $EnableException
    )
    
    # Test whether the PSReadline Module is loaded
    $PSReadline = Get-Module PSReadline
    
    #region Utility Functions
    function Set-ShellWindowWidth
    {
        [CmdletBinding()]
        Param (
            $WindowWidth
        )
        
        $currentWindow = $host.ui.rawui.WindowSize
        $currentBuffer = $host.ui.rawui.Buffersize
        
        if ($currentBuffer.Width -gt $WindowWidth)
        {
            # Set Window
            $currentWindow.Width = $WindowWidth
            $host.ui.rawui.WindowSize = $currentWindow
            
            # Set Buffer
            $currentBuffer.Width = $WindowWidth
            $host.ui.rawui.Buffersize = $currentBuffer
        }
        else
        {
            # Set Buffer
            $currentBuffer.Width = $WindowWidth
            $host.ui.rawui.Buffersize = $currentBuffer
            
            # Set Window
            $currentWindow.Width = $WindowWidth
            $host.ui.rawui.WindowSize = $currentWindow
        }
    }
    #endregion Utility Functions
    
    #region Set Buffer
    if (Test-PSFParameterBinding -ParameterName "BufferLength")
    {
        $currentBuffer = $host.ui.rawui.Buffersize
        $currentBuffer.Height = $BufferLength
        $host.ui.rawui.Buffersize = $currentBuffer
    }
    #endregion Set Buffer
    
    #region Set Foreground Color
    if (Test-PSFParameterBinding -ParameterName "ForegroundColor")
    {
        $host.ui.rawui.ForegroundColor = $ForegroundColor
        
        if ($PSReadline.Version.Major -eq 1)
        {
            Set-PSReadlineOption -ContinuationPromptForegroundColor $ForegroundColor
            Set-PSReadlineOption -ForegroundColor $ForegroundColor -TokenKind 'Comment'
            Set-PSReadlineOption -ForegroundColor $ForegroundColor -TokenKind None
        }
        elseif ($PSReadline.Version.Major -gt 1)
        {
            Set-PSReadLineOption -Colors @{
                Default = $ForegroundColor
                Comment = $ForegroundColor
                ContinuationPrompt = $ForegroundColor
            }
        }
    }
    #endregion Set Foreground Color
    
    #region Set Background Color
    if (Test-PSFParameterBinding -ParameterName "BackgroundColor")
    {
        $host.ui.rawui.BackgroundColor = $BackgroundColor
        if ($PSReadline.Version.Major -eq 1)
        {
            Set-PSReadlineOption -ContinuationPromptBackgroundColor $BackgroundColor
            Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'None'
            Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Comment'
            Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Keyword'
            Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'String'
            Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Operator'
            Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Variable'
            Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Command'
            Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Type'
            Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Number'
            Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Member'
            Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Parameter'
            Set-PSReadlineOption -EmphasisBackgroundColor $BackgroundColor
            Set-PSReadlineOption -ErrorBackgroundColor $BackgroundColor
        }
    }
    #endregion Set Background Color
    
    #region Set Window Title
    if (Test-PSFParameterBinding -ParameterName "WindowTitle")
    {
        $host.ui.rawui.Windowtitle = $WindowTitle
    }
    #endregion Set Window Title
    
    #region Set Window Width
    if (Test-PSFParameterBinding -ParameterName "WindowWidth")
    {
        try { Set-ShellWindowWidth -WindowWidth $WindowWidth -ErrorAction Stop }
        catch
        {
            Stop-PSFFunction -Message "Failed to set window width to $WindowWidth" -EnableException $EnableException -ErrorRecord $_ -Tag 'fail', 'width', 'console', 'window'
            return
        }
    }
    #endregion Set Window Width
}

function Start-PSUTimer
{
<#
    .SYNOPSIS
        Creates a timer that will alarm the user after it has expired.
     
    .DESCRIPTION
        Creates a timer that will alarm the user after it has expired.
        Provides both visual and sound warnings.
        Also provides a progress bar with a time remaining display.
     
    .PARAMETER Duration
        The time to wait.
     
    .PARAMETER Message
        What to wait for.
     
    .PARAMETER AlarmCount
        How often to give warning.
     
    .PARAMETER NoProgress
        Disables progress bar.
     
    .PARAMETER AlarmInterval
        In what time interval to write warnings and send sound.
     
    .PARAMETER MinFrequency
        The minimum frequency of the beeps.
        Must be at least one lower than MaxFrequency.
        Increase delta to play random frequency sounds on each beep.
     
    .PARAMETER MaxFrequency
        The maximum frequency of the beeps.
        Must be at least one higher than MaxFrequency.
        Increase delta to play random frequency sounds on each beep.
     
    .EXAMPLE
        PS C:\> timer 170 Tea
         
        After 170 Duration give warning that the tea is ready.
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory = $true)]
        [Alias('Seconds')]
        [PSFDateTime]
        $Duration,
        
        [Parameter(Position = 1, Mandatory = $true)]
        $Message,
        
        [Parameter(Position = 2)]
        [int]
        $AlarmCount = 25,
        
        [switch]
        $NoProgress,
        
        [int]
        $AlarmInterval = 250,
        
        [int]
        $MinFrequency = 2999,
        
        [int]
        $MaxFrequency = 3000
    )
    
    begin
    {
        $start = Get-Date
        $end = $Duration.Value
        
        function Get-FriendlyTime
        {
            [CmdletBinding()]
            param (
                [int]
                $Seconds
            )
            
            $tempSeconds = $Seconds
            $strings = @()
            if ($tempSeconds -gt 3599)
            {
                [int]$count = [math]::Floor(($tempSeconds / 3600))
                $strings += "{0}h" -f $count
                $tempSeconds = $tempSeconds - ($count * 3600)
            }
            
            if ($tempSeconds -gt 59)
            {
                [int]$count = [math]::Floor(($tempSeconds / 60))
                $strings += "{0}m" -f $count
                $tempSeconds = $tempSeconds - ($count * 60)
            }
            
            $strings += "{0}s" -f $tempSeconds
            
            $strings -join " "
        }
    }
    process
    {
        if (-not $NoProgress)
        {
            Write-Progress -Activity "Waiting for $Message" -Status "Starting" -PercentComplete 0
        }
        
        while ($end -gt (Get-Date))
        {
            Start-Sleep -Milliseconds 500
            
            if (-not $NoProgress)
            {
                $friendlyTime = Get-FriendlyTime -Seconds ($end - (Get-Date)).TotalSeconds
                [int]$percent = ((Get-Date) - $start).TotalSeconds / ($end - $start).TotalSeconds * 100
                Write-Progress -Activity "Waiting for $Message" -Status "Time remaining: $($friendlyTime)" -PercentComplete ([System.Math]::Min($percent, 100))
            }
        }
        
        if (-not $NoProgress)
        {
            Write-Progress -Activity "Waiting for $Message" -Completed
        }
        
        $countAlarm = 0
        while ($countAlarm -lt $AlarmCount)
        {
            Write-PSFHostColor -String "(<c='sub'>$countAlarm</c>) ### <c='em'>$($Message)</c> ###"
            [System.Console]::Beep((Get-Random -Minimum $MinFrequency -Maximum $MaxFrequency), $AlarmInterval)
            Start-Sleep -Milliseconds $AlarmInterval
            $countAlarm++
        }
    }
}
Import-PSUAlias -Name "timer" -Command "Start-PSUTimer"

function Get-PSUPathAlias
{
<#
    .SYNOPSIS
        Gets the PSUPathAlias configuration values.
 
    .DESCRIPTION
        Gets the PSUPathAlias configuration values from the PSFConfig system.
 
    .PARAMETER Alias
        This is the name of the alias that you want for Set-PSUPath. Wildcards accepted
 
        Default Value: *
 
    .EXAMPLE
        PS C:\> Get-PSUPathAlias
     
        Returns all aliases
#>

    [CmdletBinding()]
    param (
        [string]
        $Alias = '*'
    )
    
    $aliases = Get-PSFConfig -FullName psutil.pathalias.$Alias
    
    foreach ($currentAlias in $aliases)
    {
        [pscustomobject]@{
            Alias = ($currentAlias.fullname -replace '^psutil.pathalias.')
            Path  = $currentAlias.value
        }
    }
}


function Remove-PSUPathAlias
{
<#
    .SYNOPSIS
        Removes a path alias fromm the configuration system.
     
    .DESCRIPTION
        Removes a path alias from the configuration system using Unregister-PSFConfig.
        Note: This command has no effect on configuration setings currently in memory.
     
    .PARAMETER Alias
        The name of the Alias that you want to remove from the configuration system.
     
    .EXAMPLE
        PS C:\> Remove-PSUPathAlias -Alias work
     
        Removes the path alias named work from the configuration system.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        [Parameter(ValuefromPipelineByPropertyName = $true)]
        $Alias
    )
    
    process
    {
        Get-PSFConfig -FullName psutil.pathalias.$Alias | Unregister-PSFConfig
        Remove-PSFAlias -Name $Alias
    }
}

function Set-PSUPath
{
<#
    .SYNOPSIS
        Detects the alias that called it and sets the location to the corresponding path found in the configuration system.
 
    .DESCRIPTION
        Detects the alias that called it and sets the location to the corresponding path.
        This function will normally be called using an alias that gets set by using Set-PSUPathAlias.
 
    .PARAMETER Alias
        This is the name of the alias that called the command.
        Default Value is $MyInvocation.InvocationName and is detected automatically
 
    .PARAMETER EnableException
        Replaces user friendly yellow warnings with bloody red exceptions of doom!
        Use this if you want the function to throw terminating errors you want to catch.
 
    .EXAMPLE
        PS C:\> Software
        PS C:\Software>
 
        In this example 'Software' is an alias for Set-PSUPath that was created by using Set-PSUPathAlias.
        Set-PSUPath detected that 'Software' was the alias that called it and then sends it to the path.
        It receives the path from Get-PSUPathAlias 'software'
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        [string]
        $Alias = $MyInvocation.InvocationName,
        
        [switch]
        $EnableException
    )
    
    begin
    {
        function Resolve-PsuPath
        {
            [OutputType([System.String])]
            [CmdletBinding()]
            param (
                [string]
                $Path
            )
            
            $environ = @{}
            foreach ($item in Get-ChildItem env:)
            {
                $environ[$item.Name] = $item.Value
            }
            $pattern = '%{0}%' -f ($environ.Keys -join '%|%')
            $replacement = {
                param (
                    [string]
                    $Match
                )
                $environ = @{ }
                foreach ($item in Get-ChildItem env:)
                {
                    $environ[$item.Name] = $item.Value
                }
                $environ[$Match.Trim('%')]
            }
            [regex]::Replace($Path, $pattern, $replacement, 'IgnoreCase')
        }
    }
    process
    {
        try
        {
            $psfConfigPath = Get-PSFConfigValue -FullName psutil.pathalias.$Alias -NotNull
        }
        
        catch
        {
            $paramStopPSFFunction = @{
                Message            = "Unable to find a path setting for the alias"
                Category        = 'InvalidOperation'
                Tag                = 'fail'
                ErrorRecord        = $_
                EnableException = $EnableException
            }
            
            Stop-PSFFunction @paramStopPSFFunction
            return
        }
        
        try
        {
            Set-Location (Resolve-PsuPath -Path $psfConfigPath) -ErrorAction Stop
        }
        catch
        {
            $psfFuncParams = @{
                EnableException = $EnableException
                Message            = "Unable to set location to $psfConfigPath"
                Category        = 'InvalidOperation'
                Tag                = 'fail'
                ErrorRecord        = $_
            }
            Stop-PSFFunction @psfFuncParams
            return
        }
    }
}

function Set-PSUPathAlias
{
<#
    .SYNOPSIS
        Used to create an an alias that sets your location to the path you specify.
     
    .DESCRIPTION
        A detailed description of the Set-PSUPathAlias function.
     
    .PARAMETER Alias
        Name of the Alias that will be created for Set-PSUPath.
        Set-PSU Path detects the alias that called it and then finds the corresponding PSFConfig entry for it.
     
    .PARAMETER Path
        This is the path that you want your location to change to when the alias is called.
     
    .PARAMETER Register
        Causes PSUtil to remember the alias across sessions.
        For more advanced options, see Register-PSFConfig.
     
    .PARAMETER EnableException
        Replaces user friendly yellow warnings with bloody red exceptions of doom!
        Use this if you want the function to throw terminating errors you want to catch.
     
    .EXAMPLE
        PS C:\> Set-PSUPathAlias -Alias 'work' -Path 'C:\work'
        Creates an alias to Set-PSUPath that will set the location to 'c:\work'
     
    .EXAMPLE
        PS C:\> Set-PSUPathAlias -Alias 'repos' -Path 'C:\repos' -Register
         
        Creates an alias for repos and registers the setting so that it will persist between sessions.
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param
    (
        [Parameter(Position = 0, Mandatory)]
        [string]
        $Alias,
        
        [Parameter(Position = 1, Mandatory)]
        [string]
        $Path,
        
        [switch]
        $Register,
        
        [switch]
        $EnableException
    )
    
    try
    {
        Set-PSFConfig -FullName psutil.pathalias.$Alias -Value $Path -Description 'Sets an alias for Set-PSUPath that takes you to the path specified in the value.'
    }
    catch
    {
        $stopParams = @{
            Message            = 'Error encountered. Alias not set'
            Category        = 'InvalidOperation'
            Tag                = 'Fail'
            ErroRecord        = $_
            EnableException = $EnableException
        }
        Stop-PSFFunction @stopParams
        return
    }
    
    if ($Register)
    {
        Get-PSFConfig -FullName psutil.pathalias.$Alias | Register-PSFConfig
    }
    
    try
    {
        Import-PSUAlias -Name $Alias -Command Set-PSUPath
    }
    catch
    {
        $stopParams = @{
            Message            = 'Error. Alias not set'
            Category        = 'InvalidOperation'
            Tag                = 'Fail'
            ErroRecord        = $_
            EnableException = $EnableException
        }
        Stop-PSFFunction @stopParams
        return
    }
}


function Add-PSUString
{
<#
    .SYNOPSIS
        Makes it easy to add content to a string at pipeline.
     
    .DESCRIPTION
        Makes it easy to add content to a string at pipeline.
     
    .PARAMETER InputString
        The string(s) to add content to
     
    .PARAMETER Before
        What is prepended to the input string.
     
    .PARAMETER After
        What is appended to the input string
     
    .EXAMPLE
        1..10 | Add-PSUString "srv-ctx" "-dev"
     
        Returns a set of strings from 'srv-ctx1-dev' through 'srv-ctx10-dev'
#>

    [OutputType([System.String])]
    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipeline = $true)]
        [string[]]
        $InputString,
        
        [Parameter(Position = 0)]
        [string]
        $Before = "",
        
        [Parameter(Position = 1)]
        [string]
        $After = ""
    )
    
    begin { }
    process
    {
        foreach ($line in $InputString)
        {
            "$($Before)$($line)$($After)"
        }
    }
    end { }
}
Import-PSUAlias -Name add -Command Add-PSUString
Import-PSUAlias -Name wrap -Command Add-PSUString

function Format-PSUString
{
<#
    .SYNOPSIS
        Allows formatting objects into strings.
     
    .DESCRIPTION
        Allows formatting objects into strings.
        This is equivalent to the '-f' operator, but supports input from pipeline.
     
    .PARAMETER InputObject
        The object to format
     
    .PARAMETER Format
        The format to apply to the object
     
    .PARAMETER LotSize
        Default: 1
        How many inputo bjects should be packed into the same format string.
     
    .PARAMETER Property
        Property name(s) from the input object to use for formatting.
        If omitted, the base object will be used.
     
    .EXAMPLE
        1..5 | format "foo {0:D2}"
     
        returns "foo 01" through "foo 05"
     
    .EXAMPLE
        1..6 | format "foo {0:D3}-{1:D3}" -LotSize 2
     
        returns "foo 001-002","foo 003-004","foo 005-006"
     
    .EXAMPLE
        Get-ChildItem | Format-PSUString '{0} : {1}' -Property Name, Length
     
        Returns a list of strings, using the Name and Length property of the items in the current folder.
#>

    [OutputType([System.String])]
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]
        $InputObject,
        
        [Parameter(Position = 0, Mandatory = $true)]
        [string]
        $Format,
        
        [int]
        $LotSize = 1,
        
        [string[]]
        $Property
    )
    
    begin
    {
        $values = @()
    }
    process
    {
        foreach ($item in $InputObject)
        {
            $values += $item
            if ($values.Count -ge $LotSize)
            {
                if ($Property)
                {
                    $propertyValues = @()
                    foreach ($value in $values)
                    {
                        foreach ($propertyName in $Property)
                        {
                            $propertyValues += $value.$propertyName
                        }
                    }
                    $Format -f $propertyValues
                }
                else
                {
                    $Format -f $values
                }
                $values = @()
            }
        }
    }
    end
    {
        if ($values.Count -gt 0)
        {
            if ($Property)
            {
                $propertyValues = @()
                foreach ($value in $values)
                {
                    foreach ($propertyName in $Property)
                    {
                        $propertyValues += $value.$propertyName
                    }
                }
                $Format -f $propertyValues
            }
            else
            {
                $Format -f $values
            }
        }
    }
}
Import-PSUAlias -Name format -Command Format-PSUString

function Join-PSUString
{
<#
    .SYNOPSIS
        Joins input strings together.
     
    .DESCRIPTION
        Joins input strings together.
     
    .PARAMETER InputString
        The strings to join
     
    .PARAMETER With
        What to join the strings with.
     
    .PARAMETER BatchSize
        Default: 0
        How many to join together at a time.
        If 0 or lower are specfied, all strings will be joined together.
        Otherwise, it will join [BatchSize] strigns together at a time.
     
    .EXAMPLE
        1..9 | join ","
     
        Returns "1,2,3,4,5,6,7,8,9"
     
    .EXAMPLE
        1..9 | join "," -BatchSize 5
     
        Returns "1,2,3,4,5", "6,7,8,9"
#>

    [OutputType([System.String])]
    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipeline = $true)]
        [string[]]
        $InputString,
        
        [Parameter(Position = 0)]
        [string]
        $With = ([System.Environment]::NewLine),
        
        [int]
        $BatchSize
    )
    
    begin
    {
        $lines = @()
    }
    process
    {
        foreach ($line in $InputString)
        {
            $lines += $line
            
            if (($BatchSize -gt 0) -and ($lines.Count -ge $BatchSize))
            {
                $lines -join $With
                $lines = @()
            }
        }
    }
    end
    {
        $lines -join $With
    }
}
Import-PSUAlias -Name join -Command Join-PSUString

function Remove-PSUString
{
<#
    .SYNOPSIS
        Trims a string.
     
    .DESCRIPTION
        Trims a string.
     
    .PARAMETER InputString
        The string that will be trimmed
     
    .PARAMETER What
        What should be trimmed?
     
    .PARAMETER Start
        Should only the start be trimmed?
     
    .PARAMETER End
        Should only the end be trimmed?
     
    .EXAMPLE
        Get-Content file.txt | trim
     
        Retrieves all content from file.txt, then trims each line.
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [OutputType([System.String])]
    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipeline = $true)]
        [string[]]
        $InputString,
        
        [Parameter(Position = 0)]
        [string]
        $What = "",
        
        [switch]
        $Start,
        
        [switch]
        $End
    )
    
    process
    {
        foreach ($line in $InputString)
        {
            if ($Start -and (-not $End)) { $line.TrimStart($What) }
            elseif ((-not $Start) -and $End) { $line.TrimEnd($What) }
            else { $line.Trim($What) }
        }
    }
}
Import-PSUAlias -Name trim -Command Remove-PSUString

function Set-PSUString
{
<#
    .SYNOPSIS
        Replaces a part of the input string with another.
     
    .DESCRIPTION
        Replaces a part of the input string with another.
        Supports both regex replace as well as regular .replace().
     
    .PARAMETER InputString
        The stringgs on which replacement will be performed.
     
    .PARAMETER What
        What should be replace?
     
    .PARAMETER With
        With what should it be replaced?
     
    .PARAMETER Simple
        By default, this function uses regex replace. Sometimes this may not be desirable.
        This switch enforces simple replacement, not considering any regular expression functionality.
     
    .PARAMETER Options
        Default: IgnoreCase
        When using regex replace, it may become desirable to specify options to the system.
     
    .PARAMETER EnableException
        Replaces user friendly yellow warnings with bloody red exceptions of doom!
        Use this if you want the function to throw terminating errors you want to catch.
     
    .EXAMPLE
        "abc ABC" | replace b d
     
        Returns "adc AdC".
     
    .EXAMPLE
        "abc ABC" | replace b d -Options None
     
        Returns "adc ABC"
     
    .EXAMPLE
        "abc \def" | replace "\de" "&ed" -s
     
        Returns "abc &edf"
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [OutputType([System.String])]
    [CmdletBinding(DefaultParameterSetName = "regex")]
    Param (
        [Parameter(ValueFromPipeline = $true)]
        [string[]]
        $InputString,
        
        [Parameter(Position = 0, Mandatory = $true)]
        [string]
        $What,
        
        [Parameter(Position = 1, Mandatory = $true)]
        [AllowEmptyString()]
        [object]
        $With,
        
        [Parameter(ParameterSetName = "Simple")]
        [Alias('s')]
        [switch]
        $Simple,
        
        [Parameter(ParameterSetName = "Regex")]
        [System.Text.RegularExpressions.RegexOptions]
        $Options = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase,
        
        [switch]
        $EnableException
    )
    
    begin
    {
        if ($With -isnot [System.Management.Automation.ScriptBlock])
        {
            $With = "$With"
        }
        elseif ($Simple)
        {
            Stop-PSFFunction -Message "Cannot do a lambda replace with a simple string replacement. Please specify a string or remove the simple parameter." -EnableException $EnableException -Category InvalidArgument -Tag 'fail','validate'
            return
        }
    }
    process
    {
        if (Test-PSFFunctionInterrupt) { return }
        
        foreach ($line in $InputString)
        {
            try
            {
                if ($Simple) { $line.Replace($What, $With) }
                else { [regex]::Replace($line, $What, $With, $Options) }
            }
            catch
            {
                Stop-PSFFunction -Message "Failed to replace line" -EnableException $EnableException -ErrorRecord $_ -Tag 'Fail', 'record' -Continue
            }
        }
    }
}
Import-PSUAlias -Name replace -Command Set-PSUString

function Split-PSUString
{
<#
    .SYNOPSIS
        Splits a string. In a pipeline.
     
    .DESCRIPTION
        Splits a string. In a pipeline.
     
    .PARAMETER InputString
        The string(s) to split
     
    .PARAMETER With
        Default: "`n"
        What to split the string with
     
    .PARAMETER Simple
        Whether to disable regex when splitting.
     
    .PARAMETER Options
        Regex options to consider
     
    .EXAMPLE
        "abc,def" | split ","
     
        Returns "abc","def"
#>

    [OutputType([System.String[]])]
    [CmdletBinding(DefaultParameterSetName = "Regex")]
    Param (
        [Parameter(ValueFromPipeline = $true)]
        [string[]]
        $InputString,
        
        [Parameter(Position = 0)]
        [string]
        $With = "`n",
        
        [Alias('r')]
        [Parameter(ParameterSetName = "Simple")]
        [switch]
        $Simple,
        
        [Parameter(ParameterSetName = "Regex")]
        [System.Text.RegularExpressions.RegexOptions]
        $Options = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
    )
    
    begin
    {
        $IsRegex = $PSCmdlet.ParameterSetName -eq "Regex"
    }
    process
    {
        foreach ($line in $InputString)
        {
            if ($IsRegex) { [regex]::Split($line, $With, $Options) }
            else { $line.Split($With) }
        }
    }
    end { }
}
Import-PSUAlias -Name split -Command Split-PSUString

Register-PSFTeppScriptblock -Name psutil-convert-object-from -ScriptBlock {
    [PSUtil.Object.ObjectHost]::Conversions.Values.From | Select-Object -Unique
}

Register-PSFTeppScriptblock -Name psutil-convert-object-to -ScriptBlock {
    [PSUtil.Object.ObjectHost]::Conversions.Values | Where-Object From -EQ $fakeBoundParameter.From | Expand-PSUObject -Name To
}

Register-PSFTeppScriptBlock -Name 'PSUtil-Module-Installed' -ScriptBlock {
    (Get-PSFTaskEngineCache -Module PSUtil -Name Module).InstalledModules
}

Register-PSFTeppScriptBlock -Name 'PSUtil-Module-Total' -ScriptBlock {
    (Get-PSFTaskEngineCache -Module PSUtil -Name Module).AvailableModules
}

Register-PSFTeppScriptblock -Name 'PSUtil-Module-Repository' -ScriptBlock {
    (Get-PSFTaskEngineCache -Module PSUtil -Name Module).Repositories
}

Register-PSFTeppScriptblock -Name 'PSUtil-Module-PackageProvider' -ScriptBlock {
    (Get-PSFTaskEngineCache -Module PSUtil -Name Module).PackageProvider
}

Register-PSFTeppScriptblock -Name 'psutil.prompt' -ScriptBlock {
    $module = Get-Module PSUtil
    & $module {
        foreach ($item in (Get-ChildItem "$script:ModuleRoot\internal\prompts"))
        {
            $item.BaseName -replace '\.prompt$'
        }
    }
}

Register-PSFTeppScriptblock -Name psutil-userprofile -ScriptBlock {
    Get-ChildItem "$env:SystemDrive\Users" -Force | Where-Object PSIsContainer | Expand-PSUObject Name
}

Register-PSFTeppArgumentCompleter -Command Invoke-PSUDesktop -Parameter User -Name psutil-userprofile

Register-PSFTeppArgumentCompleter -Command Convert-PSUObject -Parameter From -Name psutil-convert-object-from
Register-PSFTeppArgumentCompleter -Command Convert-PSUObject -Parameter To -Name psutil-convert-object-to

Register-PSFTeppArgumentCompleter -Command Set-PSUPrompt -Parameter Prompt -Name 'psutil.prompt'

#region Module
Register-PSFTeppArgumentCompleter -Command Update-Module -Parameter Name -Name 'PSUtil-Module-Installed'
Register-PSFTeppArgumentCompleter -Command Uninstall-Module -Parameter Name -Name 'PSUtil-Module-Installed'
Register-PSFTeppArgumentCompleter -Command Get-InstalledModule -Parameter Name -Name 'PSUtil-Module-Installed'
Register-PSFTeppArgumentCompleter -Command Install-Module -Parameter Name -Name 'PSUtil-Module-Total'

Register-PSFTeppArgumentCompleter -Command Find-Command -Parameter Repository -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Find-DscResource -Parameter Repository -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Find-Module -Parameter Repository -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Find-RoleCapability -Parameter Repository -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Find-Script -Parameter Repository -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Install-Module -Parameter Repository -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Install-Script -Parameter Repository -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Publish-Module -Parameter Repository -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Publish-Script -Parameter Repository -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Save-Module -Parameter Repository -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Save-Script -Parameter Repository -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Get-PSRepository -Parameter Name -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Unregister-PSRepository -Parameter Name -Name 'PSUtil-Module-Repository'
Register-PSFTeppArgumentCompleter -Command Register-PSRepository -Parameter PackageManagementProvider -Name 'PSUtil-Module-PackageProvider'
#endregion Module

#region Input Object Property
Register-PSFTeppArgumentCompleter -Command Select-Object -Parameter Property -Name PSFramework-Input-ObjectProperty
Register-PSFTeppArgumentCompleter -Command Select-Object -Parameter ExpandProperty -Name PSFramework-Input-ObjectProperty
Register-PSFTeppArgumentCompleter -Command Select-Object -Parameter ExcludeProperty -Name PSFramework-Input-ObjectProperty

Register-PSFTeppArgumentCompleter -Command Expand-PSUObject -Parameter Name -Name PSFramework-Input-ObjectProperty
#endregion Input Object Property

New-PSFLicense -Product 'PSUtil' -Manufacturer 'Friedrich Weinmann' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2018-12-16") -Text @"
Copyright (c) 2018 Friedrich Weinmann
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"@


<#
In this file, all default expansion definitions are stored.
Wrap into a region each, with corresponding region label.
#>


#region Microsoft.PowerShell.Commands.MatchInfo
Register-PSUObjectExpansion -TypeName "Microsoft.PowerShell.Commands.MatchInfo" -ScriptBlock {
    param (
        $Object
    )
    
    foreach ($item in $Object.Matches)
    {
        $item.Groups[1 .. ($item.Groups.Count - 1)].Value
    }
}
#endregion Microsoft.PowerShell.Commands.MatchInfo

#region Microsoft.PowerShell.Commands.MemberDefinition
Register-PSUObjectExpansion -TypeName "Microsoft.PowerShell.Commands.MemberDefinition" -ScriptBlock {
    param (
        $Object
    )
    
    $Object.Definition.Replace("), ", ")þ").Split("þ")
}
#endregion Microsoft.PowerShell.Commands.MemberDefinition

#region System.Management.Automation.FunctionInfo
Register-PSUObjectExpansion -TypeName "System.Management.Automation.FunctionInfo" -ScriptBlock {
    param (
        $Object
    )
    
    @"
function $($Object.Name)
{
$($Object.Definition)
}
"@

}
#endregion System.Management.Automation.FunctionInfo

#region System.Management.Automation.AliasInfo
Register-PSUObjectExpansion -TypeName "System.Management.Automation.AliasInfo" -ScriptBlock {
    param (
        $Object
    )
    
    if ($Object.ResolvedCommand.CommandType -eq "Function")
    {
        @"
function $($Object.ResolvedCommand.Name)
{
$($Object.ResolvedCommand.Definition)
}
"@

    }
    else
    {
        $Object.ResolvedCommand
    }
}
#endregion System.Management.Automation.AliasInfo

# The king of aliases
Import-PSUAlias -Name "grep" -Command "Select-String"

# Add simple aliases for everyday cmdlets
Import-PSUAlias -Name "a" -Command "Get-Alias"
Import-PSUAlias -Name "c" -Command "Get-Command"
Import-PSUAlias -Name "m" -Command "Measure-Object"
Import-PSUAlias -Name "v" -Command "Get-Variable"

# Add aliases for frequent export commands
Import-PSUAlias -Name "ix" -Command "Import-PSFClixml"
Import-PSUAlias -Name "ex" -Command "Export-PSFClixml"
Import-PSUAlias -Name "ic" -Command "Import-Csv"
Import-PSUAlias -Name "ec" -Command "Export-Csv"

# Add alias for easy clipboarding
Import-PSUAlias -Name "ocb" -Command "Set-Clipboard"

# Add alias for creating object
Import-PSUAlias -Name "new" -Command "New-Object"

# Add alias for the better select and to avoid breaking on old command
Import-PSUAlias -Name "spo" -Command "Select-PSFObject"
Import-PSUAlias -Name "Select-PSUObject" -Command "Select-PSFObject"

if (Get-PSFConfigValue -FullName 'PSUtil.Import.Alias.SystemOverride')
{
    Remove-PSFAlias -Name select -Force
    Remove-PSFAlias -Name gm -Force
    Import-PSUAlias -Name select -Command 'Select-PSFObject'
    Import-PSUAlias -Name gm -Command 'Get-PSMDMember'
}

if ((Get-Module psreadline).Version.Major -ge 2)
{
    Set-PSReadlineKeyHandler -Chord 'Shift+SpaceBar' -BriefDescription Whitespace -Description "Inserts a whitespace" -ScriptBlock {
        [Microsoft.Powershell.PSConsoleReadLine]::Insert(' ')
    }
}

if ((Get-PSFConfigValue -FullName "PSUtil.Import.Keybindings" -Fallback $true) -and (Get-Module PSReadline))
{
    foreach ($file in (Get-ChildItem -Path (Join-PSFPath $script:ModuleRoot 'internal' 'keybindings')))
    {
        . Import-ModuleFile -Path $file.FullName
    }
}


Register-PSUObjectConversion -From dec -To bin -ScriptBlock {
    Param (
        $InputObject
    )
    
    [System.Convert]::ToString($InputObject, 2)
}

Register-PSUObjectConversion -From bin -To dec -ScriptBlock {
    Param (
        $InputObject
    )
    
    [System.Convert]::ToInt32($InputObject, 2)
}

Register-PSUObjectConversion -From dec -To hex -ScriptBlock {
    Param (
        $InputObject
    )
    
    [System.Convert]::ToString($InputObject, 16)
}

Register-PSUObjectConversion -From hex -To dec -ScriptBlock {
    Param (
        $InputObject
    )
    
    [System.Convert]::ToInt32($InputObject, 16)
}


Register-PSUObjectConversion -From dec -To oct -ScriptBlock {
    Param (
        $InputObject
    )
    
    [System.Convert]::ToString($InputObject, 8)
}

Register-PSUObjectConversion -From oct -To dec -ScriptBlock {
    Param (
        $InputObject
    )
    
    [System.Convert]::ToInt32($InputObject, 8)
}


Register-PSUObjectConversion -From hex -To bin -ScriptBlock {
    Param (
        $InputObject
    )
    
    $temp = [System.Convert]::ToInt32($InputObject, 16)
    [System.Convert]::ToString($temp, 2)
}

Register-PSUObjectConversion -From bin -To hex -ScriptBlock {
    Param (
        $InputObject
    )
    
    $temp = [System.Convert]::ToInt32($InputObject, 2)
    [System.Convert]::ToString($temp, 16)
}


Register-PSUObjectConversion -From hex -To oct -ScriptBlock {
    Param (
        $InputObject
    )
    
    $temp = [System.Convert]::ToInt32($InputObject, 16)
    [System.Convert]::ToString($temp, 8)
}

Register-PSUObjectConversion -From oct -To hex -ScriptBlock {
    Param (
        $InputObject
    )
    
    $temp = [System.Convert]::ToInt32($InputObject, 8)
    [System.Convert]::ToString($temp, 16)
}


Register-PSUObjectConversion -From bin -To oct -ScriptBlock {
    Param (
        $InputObject
    )
    
    $temp = [System.Convert]::ToInt32($InputObject, 2)
    [System.Convert]::ToString($temp, 8)
}

Register-PSUObjectConversion -From oct -To bin -ScriptBlock {
    Param (
        $InputObject
    )
    
    $temp = [System.Convert]::ToInt32($InputObject, 8)
    [System.Convert]::ToString($temp, 2)
}


Register-PSUObjectConversion -From script -To encoded -ScriptBlock {
    Param (
        $InputObject
    )
    
    $bytes = [System.Text.Encoding]::Unicode.GetBytes($InputObject)
    [Convert]::ToBase64String($bytes)
}

Register-PSUObjectConversion -From encoded -To script -ScriptBlock {
    Param (
        $InputObject
    )
    
    $bytes = [System.Convert]::FromBase64String($InputObject)
    [System.Text.Encoding]::Unicode.GetString($bytes)
}


Set-PSFTaskEngineCache -Module PSUtil -Name Module -Lifetime 5m -Collector {
    $paramRegisterPSFTaskEngineTask = @{
        Name        = 'PSUtil.ModuleCache'
        Description = 'Refreshes the data on locally available modules and repositories'
        Once        = $true
        ResetTask   = $true
        ScriptBlock = {
            $data = @{
                InstalledModules = ((Get-InstalledModule).Name | Select-Object -Unique)
                AvailableModules  = ((Get-Module -ListAvailable).Name | Select-Object -Unique)
                PackageProvider  = ((Get-PackageProvider).Name)
                Repositories = ((Get-PSRepository).Name)
            }
            Set-PSFTaskEngineCache -Module PSUtil -Name Module -Value $data
        }
    }
    
    Register-PSFTaskEngineTask @paramRegisterPSFTaskEngineTask
    
    while (-not [PSFramework.TaskEngine.TaskHost]::GetCacheItem('PSUtil', 'Module').Value)
    {
        Start-Sleep -Milliseconds 100
    }
    
    # Deliver expired data right away anyway
    [PSFramework.TaskEngine.TaskHost]::GetCacheItem('PSUtil', 'Module').Value
}
#endregion Load compiled code