PSPrompt.psm1

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

# Detect whether at some level dotsourcing was enforced
$script:doDotSource = $false # Get-PSFConfigValue -FullName PSPrompt.Import.DoDotSource -Fallback $false
if ($PSPrompt_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 = $false # Get-PSFConfigValue -FullName PSPrompt.Import.IndividualFiles -Fallback $false
if ($PSPrompt_importIndividualFiles) { $importIndividualFiles = $true }
#if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true }
if (Test-Path "$($script:ModuleRoot)\..\.git") { $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
#region code to execute every Nth command
#TODO:: remove hard-coded 5 and replace with user option
if ((Get-History -Count 1).ID % 5 -eq 0) {
    if ($BatteryHistory -or $Git) {
        #region Git status every Nth command
            $r = git status
            $NewFiles = ($r -match 'new file:').count
            $ModFiles = ($r -match 'modified:').count
            $DelFiles = ($r -match 'deleted:').count
            $Branch = $r[0]

            Write-Host "git status: " -ForegroundColor White -BackgroundColor Black -NoNewline
            if ($Branch -eq "On branch master") { Write-Host "*WARNING : Working in MASTER.*" -ForegroundColor Yellow -BackgroundColor Red -NoNewline }
            else { Write-Host "$r[0]" -ForegroundColor Blue -BackgroundColor Black -NoNewline }
            Write-Host " New[$NewFiles] " -ForegroundColor Green -BackgroundColor Black -NoNewline
            Write-Host " Mod[$ModFiles] " -ForegroundColor DarkCyan -BackgroundColor Black -NoNewline
            Write-Host " Del[$DelFiles] " -ForegroundColor Red -BackgroundColor Black
        #endregion

        #region BatteryHistory
        if ($BatteryHistory) { # not live yet so being kept apart
            #region BatteryHistory logging
            $b = (Get-CimInstance -ClassName CIM_Battery)
            Write-PSPLog -Message "$($b.EstimatedChargeRemaining)" -Source "BatteryPct"
            $Battery = @{
                IsCharging = if ($b.BatteryStatus -eq 1) { $false } else { $true }
                Charge     = $b.EstimatedChargeRemaining.GetValue(1)
                Remaining  = $b.EstimatedRunTime.GetValue(1)
            }
            $msg = "Charging:{0}; ChargePct:{1}; Remaining:{2}" -f $Battery.IsCharging, ($Battery.Charge), $Battery.Remaining
            $msg
            Write-PSPLog -Message $msg -Source "BatteryStatus"
            #endregion

            #region BatteryHistory display
            $Hdr = "Date", "Source", "Message"
            $Hist = Import-Csv -Path "C:\temp\PSPLog.log" -Delimiter "`t" -Header $Hdr
            $BHist = $Hist | Where-Object source -eq "Batterypct" | Sort-Object date -Descending

            $Drain = $BHist[0, 1] | Select-Object date, message
            # $Drain[1].message - $Drain[0].message
            $Duration = New-TimeSpan -Start ([datetime]::parseexact($Drain[1].Date, "yyyyMMddHHmmss", $null)) -End ([datetime]::parseexact($Drain[0].Date, "yyyyMMddHHmmss", $null) )
            $PowerLoss = ($Drain[1].message - $Drain[0].message) / $Duration.TotalMinutes # Percent loss per minute
            write-verbose ("Battery % loss per min {0:N1}" -f $PowerLoss)
            $msg ="[{0:N0}m]" -f ($Battery.Charge / $PowerLoss)

            Write-Host $msg
            #endregion
        }
        #endregion
    }
}
#endregion

function Prompt {
    <#
    .Synopsis
    Your custom PowerShell prompt
 
    # origins from https://dbatools.io/prompt but formatting the execution time without using the DbaTimeSpanPretty C# type
 
    .Description
    Custom prompt that includes the following features:
    - Admin user : if the current session is running as Administrator then the prompt shows [Admin] in black on red
    - Battery : if you are working on your battery - % charged and est min remaining
    - Day and date : the current day and date are shown at the left side of the prompt in all use-cases
    - UTC offset : if the system timezone is not UTC then the offset in hours is shown Red/Green for + or - difference
    - fun function : put some fun into your PowerShell prompt with a countdown to your next big event ()
    - current path : shortens the path if there are more than 2 directories and truncates those to 7 characters
    - last command : execution duration of the last command executed
 
    .Example
    Prompt
     
    There is no valid example for this function - it runs automatically as your console completes commands
     
    #>

    [outputtype([system.string])]
    [CmdletBinding()]
    #[CmdletBinding(DefaultParameterSetName = 'Custom')]
    Param(
        # [parameter(parametersetname = "Custom")][switch]$AddToProfile,
        # [parameter(parametersetname = "Custom")][switch]$Admin,
        # [parameter(parametersetname = "Custom")][switch]$Battery,
        # [parameter(ParameterSetName = "Reset")][switch]$Reset,
        # [parameter(ParameterSetName = "Custom")][switch]$Only # reserved for future use
    )
    # if ($Reset) {
    # $PromptOptions = get-item "$env:APPDATA\prompt*.ps1" | Select-Object name, LastWriteTime
    #
    # Write-Output "Prompt returned to original state"
    # return
    # }
    #region Show if using Administrator level account
    #if ($admin) {
    $principal = [Security.Principal.WindowsPrincipal] ([Security.Principal.WindowsIdentity]::GetCurrent())
    $adm = [Security.Principal.WindowsBuiltInRole]::Administrator
    if ($principal.IsInRole($adm)) {
        write-host -ForegroundColor "Black" -BackgroundColor "DarkRed" "[ADMIN]" -NoNewline
    }
    # "added admin"
    #}
    #endregion

    #region Battery status
    $b = (Get-CimInstance -ClassName CIM_Battery)
    $Battery = $null
    $Battery = @{
        IsCharging = if ($b.BatteryStatus -eq 1) { $false } else { $true }
        Charge     = $b.EstimatedChargeRemaining.GetValue(1)
        Remaining  = $b.EstimatedRunTime.GetValue(1)
    }
    if ($Battery.Remaining -gt 90) {
        $Battery.Remaining = "90m+"
    }
    else {
        $Battery.Remaining = "$($Battery.Remaining)m"
    }

    Write-Verbose $Battery

    if (!($Battery.IsCharging)) {
        $msg = $b = $extramsg = $null
        switch ($Battery.Charge[0]) {

            { $_ -gt 80 } {
                $colour = "Green"
                break
            }
            { $_ -gt 60 } {
                $colour = "DarkGreen"
                break
            }
            { $_ -gt 40 } {
                $colour = "DarkRed"
                break
            }
            { $_ -gt 20 } {
                $colour = "Red"
                break
            }
            default {
                $extramsg = "`r`nBATTERY VERY LOW :: SAVE YOUR WORK`r`n"
                $colour = "yellow"
            }
        }
        $msg = ("[{0}%:{1}{2}]" -f ($Battery.Charge), ($Battery.Remaining), $EXTRAmsg )
        Write-Host -Object $msg -BackgroundColor $colour -ForegroundColor Black -NoNewline
    }
    #endregion

    #region Day and date
    $msg = "[{0}]" -f (Get-Date -Format "ddd HH:mm:ss")
    Write-Host $msg -NoNewline
    #endregion

    #region UTC offset
    # add info if not in home timezone
    # get offset and daylight savings name
    $tz = Get-TimeZone
    $offset = $tz.BaseUtcOffset # need to place YOUR normal timezone here
    [timespan]$adjustment = 0
    # if we are in daylight saving then the offset from home will be 60 mins, not 0
    if ($tz.id -eq $tz.DaylightName) {
        $adjustment = New-TimeSpan -Minutes 60
    }
    $fc = "white"
    $p = "GMT"
    if (($offset.TotalMinutes + $adjustment.TotalMinutes) -ne 0) {
        [double]$h = $offset.TotalMinutes / 60
        if ($h -lt 0) {
            $sign = ""
            $fc = "Red"
        }
        else {
            $sign = "+"
            $fc = "Green"
        }
        $p = "(UK $sign$($h.ToString()))"

        write-host -ForegroundColor $fc -BackgroundColor Black $p -NoNewline
    }
    #endregion

    #region custom/fun function
    if ((get-date).Month -eq 12 -and (get-date).Day -lt 25) {
        $msg = "["
        $msg += TimetoSanta -purpose Prompt
        $msg += "]"
        Write-Host $msg -NoNewline -BackgroundColor Red -ForegroundColor DarkBlue
    }
    #endregion

    #region last command execution duration
    try {
        $history = Get-History -ErrorAction Ignore -Count 1
        if ($history) {
            $ts = New-TimeSpan $history.StartExecutionTime  $history.EndExecutionTime
            Write-Host "[" -NoNewline
            switch ($ts) {
                { $_.TotalSeconds -lt 1 } {
                    [decimal]$d = $_.TotalMilliseconds
                    '{0:f3}ms' -f ($d) | Write-Host  -ForegroundColor Black -NoNewline -BackgroundColor DarkGreen
                    break
                }
                { $_.TotalMinutesutes -lt 1 } {
                    [decimal]$d = $_.TotalSeconds
                    '{0:f3}s' -f ($d) | Write-Host  -ForegroundColor Black -NoNewline -BackgroundColor DarkYellow
                    break
                }
                { $_.TotalMinutestes -lt 30 } {
                    [decimal]$d = $ts.TotalMinutes
                    '{0:f3}m' -f ($d) | Write-Host  -ForegroundColor Gray -NoNewline  -BackgroundColor Red
                    break
                }
                Default {
                    $_.Milliseconds | Write-Host  -ForegroundColor Gray -NoNewline
                }
            }
            Write-Host "]" -NoNewline
        }
    }
    catch {
        # if there is no history then we dont put anything into the prompt
        ""
    }
    #endregion

    #region reduce the path displayed if it is long
    if (($pwd.Path.Split('\').count -gt 2)) {
        $One = $pwd.path.split('\')[-1]
        $Two = $pwd.path.split('\')[-2]

        if ($One.Length -gt 10) { $One = "$($One[0..7] -join '')~" }
        if ($Two.Length -gt 10) { $Two = "$($Two[0..7] -join '')~" }

        write-host " $($pwd.path.split('\')[0], '...', $Two, $One -join ('\'))" -NoNewline
    }
    else {
        Write-Host " $($pwd.path)" -NoNewline
    }
    #endregion
    Write-Host "> " -NoNewline
}

function Write-PSPLog {
    <#
    .Synopsis
    Worker function for PSPrompt
 
    .Description
    Called internally by PSPrompt to store log info and facilitate prompt information such as battery drain rate
 
    .Example
    Write-PSPLog -message ("It's test # $_ everybody." ) -Source "Testing"
 
    This writes a message to the PSPLog.log
 
    #>

    [cmdletbinding()]
    param(
        # message to be written to the log
        [parameter()]
        [string]$Message,
        # where the message has been sent from
        [Parameter()]
        [string]
        $Source
    )
    begin {
        $LogFile = "C:\temp\PSPLog.log"
        # check we have a logfile
        if (!(test-path $LogFile)) {
            $null = New-Item -Path $LogFile -ItemType File
        }
        else {
            # check the logfile isnt getting too big
            $file = get-item $LogFile
            if ($file.length -gt 5MB) {
                # if its big rename it and recreate it
                $date = (get-date).ToString("yyyyMMdd")
                try {
                    $archive = $($file.FullName -replace '\.log', "") + $date + "_archive.log"
                    $null = $File.CopyTo($archive)
                    Set-Content -Value "" -Path $LogFile
                    $msg = "{0}`t{1}`t{2}" -f (get-date).ToString("yyyyMMddHHmmss"), 'Internal', "previous PSPrompt Log file archived"
                    $msg | Out-File -FilePath $LogFile -Append
                }
                catch {
                    Write-Warning "Unable to archive / reset PSPrompt Log file"
                    $msg = "{0}`t{1}`t{2}" -f (get-date).ToString("yyyyMMddHHmmss"), $Source, "Unable to archive / reset PSPrompt Log file"
                    $msg | Out-File -FilePath $LogFile -Append
                    $msg = "{0}`t{1}`t{2}" -f (get-date).ToString("yyyyMMddHHmmss"), $Source, $error[0].Exception
                    $msg | Out-File -FilePath $LogFile -Append -Encoding
                }
            }
        }
    }
    process {
        $msg = "{0}`t{1}`t{2}" -f (get-date).ToString("yyyyMMddHHmmss"), $Source, $message
        $msg | Out-File -FilePath $LogFile -Append
    }
    end { }
}


function ConvertFrom-Byte {
    <#
    .SYNOPSIS
    Convert a Byte value to a KB, MB, GB, or TB value
    
    .DESCRIPTION
    Convert a Byte value to a KB, MB, GB, or TB value
    
    .PARAMETER Bytes
    The value in bytes that you want to see expressed in a higher metric
    
    .EXAMPLE
    ConvertFrom-Byte 1234342562
 
    result:
    1.15GB
    
    .EXAMPLE
    ConvertFrom-Byte 123434
 
    result:
    120.54KB
    
    #>

    [outputtype([system.string])]
    param (
        [parameter(ValueFromPipeline=$true)]
        [Alias('Length')]
        [ValidateNotNullorEmpty()]
        $Bytes
    )

    begin {}

    process {
        switch -Regex ([math]::truncate([math]::log([System.Convert]::ToInt64($Bytes), 1024))) {
            '^0' { "$Total Bytes" ; Break }
            '^1' { "{0:n2} KB" -f ($Bytes / 1KB) ; Break }
            '^2' { "{0:n2} MB" -f ($Bytes / 1MB) ; Break }
            '^3' { "{0:n2} GB" -f ($Bytes / 1GB) ; Break }
            '^4' { "{0:n2} TB" -f ($Bytes / 1TB) ; Break }
            '^5' { "{0:n2} PB" -f ($Bytes / 1PB) ; Break }
            # When we fail to have any matches, 0 Bytes is more clear than 0.00 PB (since <5GB would be 0.00 PB still)
            Default { "0 Bytes" }
        }
    }

    end {}
}


function ConvertFrom-Epoch {
    <#
    .SYNOPSIS
    Converts a unix style epoch time to a datetime value
 
    .DESCRIPTION
    Converts a unix style time value like 1559684667 and returns a datetime value like 04 June 2019 21:44:27
 
    .PARAMETER Seconds
    The integer value that should be used to convert to a date time value
 
    .EXAMPLE
    ConvertFrom-Epoch 1428665400
 
    This returns the date time value of 10 April 2015 12:30:00
 
    #>

    [outputtype([system.datetime])]

    [cmdletbinding()]
    param(
        #Seconds value since 01-Jan-1970
        [parameter(ValueFromPipeline = $true, Mandatory = $true)]
        $Seconds
    )

    [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($Seconds))
}




function ConvertTo-Epoch {
    <#
    .SYNOPSIS
    Converts a datetime to epoch int value
 
    .DESCRIPTION
    Converts a value like 10-Apr-2018 10:45:01 to an epoch style int value
 
    .PARAMETER date
    The date value that should be converted
 
    .EXAMPLE
    ConvertTo-Epoch '10-Apr-2015 12:30:00'
 
    This returns the epoch value of 1428665400
 
#>

    [outputtype([system.int32])]
    [cmdletbinding()]
    param(
        # date value to convert to epoch
        [parameter(ValueFromPipeline = $true, Mandatory = $true)][datetime]
        $date
    )
    [int][double]::Parse((Get-Date ($date).ToUniversalTime() -UFormat %s))
}


function Find-Script {
    <#
    .SYNOPSIS
   
    script finder
     
    .DESCRIPTION
     
    reviews locations known to have script files in them for specified string
   
    .EXAMPLE
    Find-Script -Search event -Type ps1
 
    Example searches for the string 'event' in filenames with extension matching ps1
   
    .EXAMPLE
    Find-Script -Search audit -Type sql -includecontent
 
    Example searches for the string 'audit' in file names and content with extension matching sql
   
     
    #>

    [CmdletBinding()]
                
    param (
        # The string you want to search for
        [parameter(Mandatory = $false)]$Search,
        # The file type you want to search for
        [parameter(Mandatory = $false)]$Type,
        # Do you want to search in file content too
        [parameter(Mandatory = $false)][switch]$IncludeContent
    )
      
    $Type = '*.' + $Type
    $List = Import-Csv 'C:\Users\jonallen\OneDrive\PowerShellLocations.csv'
  
    # $Results = [[PSCustomObject]@ {
    # MatchType = $null
    # FileName = $null
    # FilePath = $null
    # }]
    $Results = ForEach ($Item in $List) {
    
        ".. Checking $($item.name) .."
        foreach ($doc in Get-ChildItem $item.Folder -Recurse -include $Type) {
            if ($doc -match $search) {
                [pscustomobject]@{
                    MatchType = "FileName"
                    FileName  = $($doc.Name)
                    FilePath  = $($doc.FullName)
                }
            }
            else {
                if ($IncludeContent) {
                    try {
                        $content = Get-Content $doc
                        if ($content -match $Search) {
                            [pscustomobject]@{
                                MatchType = "Content"
                                FileName  = $($doc.Name)
                                FilePath  = $($doc.FullName)
                            }
                        }
                    }
                    catch {
                        write-verbose "unable to open $doc"
                    }
                }
            }
        }
    }
    return $Results
      
}

function Get-BatteryStatus {
    <#
    .SYNOPSIS
    Displays battery status to console
 
    .DESCRIPTION
    Displays battery status to console
 
    .EXAMPLE
    Get-BatteryStatus
 
    Get the battery status
       
    .EXAMPLE
    battery
 
    uses the alias to call this function
    #>

    [outputtype([system.string])]
    [alias('battery')]
    [CmdletBinding()]
    param (    )

    process {
        $b = (Get-CimInstance -ClassName CIM_Battery)

        $Battery = [PSCustomObject]@{
            IsCharging = if ($b.BatteryStatus -eq 1) { "Not Charging" } else { "Charging" }
            Charge     = $b.EstimatedChargeRemaining #.GetValue(1)
            Remaining  = $b.EstimatedRunTime #.GetValue(1)
        }
        $msg = "$($Battery.Charge)%"
        if ($Battery.IsCharging -eq "Charging") {
            $msg += " $($Battery.IsCharging) "
        }
        else {
            $msg += "/ $($Battery.Remaining) mins - Discharging"
        }
      
        $msg
    }
}

Function Get-OutlookCalendar {
    <#
   .Synopsis
    This function returns InBox items from default Outlook profile
 
   .Description
    This function returns InBox items from default Outlook profile. It
    uses the Outlook interop assembly to use the olFolderInBox enumeration.
    It creates a custom object consisting of Subject, ReceivedTime, Importance,
    SenderName for each InBox item.
    *** Important *** depending on the size of your InBox items this function
    may take several minutes to gather your InBox items. If you anticipate
    doing multiple analysis of the data, you should consider storing the
    results into a variable, and using that.
 
    .Example
    Get-OutlookCalendar -StartTime (get-date).date -EndTime ((get-date).adddays(+7)).date
 
    This returns all calendar events for the forth-coming 7 days
 
    .Example
    Get-OutlookCalendar -StartTime (get-date).date -EndTime ((get-date).adddays(+1)).date -verbose
 
    This returns all calendar events for the forth-coming 24 hours
 
    .Example
    Get-OutlookCalendar -Today | ft -a -Wrap
 
    This example uses the -Today switch to get information just for the current day. Output is formatted as a table
 
    .Notes
    # genesis of Outlook access from https://gallery.technet.microsoft.com/scriptcenter/af63364d-8b04-473f-9a98-b5ab37e6b024
    NAME: Get-OutlookInbox
    AUTHOR: ed wilson, msft
    LASTEDIT: 05/13/2011 08:36:42
    KEYWORDS: Microsoft Outlook, Office
    HSG: HSG-05-26-2011
 
   .Link
     Http://www.ScriptingGuys.com/blog
 #>

    [outputtype([system.string])]
    [alias('cal', 'event')]
    [cmdletbinding(DefaultParameterSetName = "Today")]
    param(
        # Start of time span to show calendar events.
        [Parameter(ParameterSetName = "StartEnd",
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "Start of time span to show calendar events.")]
        [datetime]$StartTime,
        # End of time span to show calendar events.
        [Parameter(ParameterSetName = "StartEnd",
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "End of time span to show calendar events.")]
        [datetime]$EndTime,
        # Show calendar events for just today.
        [Parameter(ParameterSetName = "Today",
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "Show calendar events for just today.")]
        [switch]$Today,
        # Show calendar events for next 7 days.
        [Parameter(ParameterSetName = "Next7Days",
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "Show calendar events for next 7 days.")]
        # use Next7 to get the next 7 days of calendar events
        [switch]$Next7
    )

    begin {
        Write-Verbose "command is : $command"
        Write-Verbose " folder items : $(($script:eventsfolder).count) "
        $null = Add-type -assembly "Microsoft.Office.Interop.Outlook"
        $olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]
        $outlook = new-object -comobject outlook.application
        $namespace = $outlook.GetNameSpace("MAPI")
        $script:eventsfolder = $namespace.getDefaultFolder($olFolders::olFolderCalendar)

        $msg = "Getting you outlook calendar takes a few seconds ..."
        Write-Host $msg
        $today = $true
        # just todays events
        if ($Today) {
            $StartTime = (get-date).Date
            $EndTime = ((get-date).AddDays(+1)).date
        }
        # events for the whole week
        if ($Next7) {
            $StartTime = (get-date).Date
            $EndTime = ((get-date).AddDays(+7)).date
        }
        $BusyStatus = @{
            0 = "Free"
            1 = "Tentative"
            2 = "Busy"
            3 = "Out of Office"
            4 = "Working away"
        }
    }
    process {
        # actually go and get the calendar events for the chosen period
        $cal = $script:eventsfolder.items |
        Where-Object { $_.start -gt $StartTime -and $_.start -lt $EndTime } |
        Select-Object subject, start, end, busystatus, @{name = 'Duration'; expression = { "*" * (New-TimeSpan -Start $_.start -End $_.end).TotalHours } }
        if ($cal.count -eq 0) {
            "Nothing in your calendar"
        }
        else {
            $cal | Select-Object Subject, Start, End, @{name = "Busy status"; expression = { $BusyStatus[$_.busystatus] } }, Duration
        }
    }
    end { }
} #end function



function Invoke-WebSearch {
    <#
        .SYNOPSIS
        Quick search the internet for content
 
        .DESCRIPTION
        Access your preferred search engine directly from your powershell console
 
        .PARAMETER search
        provide the search string and the search engine
 
        .EXAMPLE
        New-WebSsearch -engine google -search 'kitten gifs'
 
        Searches google for gifs of kittens
        .NOTES
        n/a
    #>

    [alias('Google','DuckDuckGo','DDG','Ask','Bing')]
    [cmdletbinding()]
    param(
        # the search string
        [parameter(ValueFromRemainingArguments=$true)][string]$search
    )
    # begin {
        if ($search -eq 'gal') {
            $url = 'https://www.powershellgallery.com/'
        }
    # }
    # process {
        $engine = (Get-PSCallStack).InvocationInfo.MyCommand.Definition[1] # check how we called the function
        switch -Regex ($engine) {
            { $_ -match ('DDG | DuckDuckGo') } { $url = "https://duckduckgo.com/?q=$search"; break }
            { $_ -match 'Google' } { $url = "https://www.google.co.uk/search?q=$Search"; break }
            { $_ -match 'Bing' } { $url = "https://www.bing.com/search?q=$Search"; break }
            { $_ -match 'Ask' } { $url = "https://uk.ask.com/web?q=$Search"; break }
            default { $url = "https://www.bing.com/search?q=$Search" }
        }
        Start-Process $url
    # }
}

function New-OutlookCalendar {
    <#
    .synopsis
    command line Outlook calendar event creation
 
    .description
    Quickly and easily add meetings and reminders to your Outlook calendar via your keyboard without leaving your PowerShell host
     
    .parameter WhatIf
    [<SwitchParameter>]
    If this switch is enabled, no actions are performed but informational messages will be displayed that
    explain what would happen if the command were to run.
 
    .parameter Confirm
    [<SwitchParameter>]
    If this switch is enabled, you will be prompted for confirmation before executing any operations that
    change state.
 
    .example
    New-OutlookCalendar -Start '20-Mar-2019 12:00' -Subject "Get birthday present for Timmy" -Status 'Out of Office'
 
    This example create a 30 minute event in the calendar at 12:00 on 20th Mar 2019 with a subject of "Get birthday present for Timmy" with a reminder
 
    .example
    New-OutlookCalendar -Start (get-date -date ((get-date).ToShortDateString())).AddHours(18) -Subject "After work drinks" -Status 'Busy' -duration 180
 
    This example create a 3 hour event in the calendar at 18:00 today with a subject of "After work drinks" with a reminder
     
    .link
    olItems <https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.outlook.olitemtype?view=outlook-pia>
    olBusyStatus https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.outlook.olbusystatus?view=outlook-pia
     
    #>

    [cmdletbinding(SupportsShouldProcess = $true)]
    param(
        # Start time of new event
        [Parameter(Mandatory = $true)]
        [datetime]$Start,
        # Subject of the new event
        [Parameter(Mandatory = $true)]
        [string]$Subject,
        # How long is the event in minutes (default - 30m)
        [Parameter()]
        [int]$Duration = 30,
        # Busy status (default - Busy)
        [Parameter()]
        [validateset('Free', 'Tentative', 'Busy', 'Out of Office', 'Working away')]
        [string]$Status = "Busy",
        # No reminder for the event
        [Parameter()]
        [switch]$NoReminder
    )

    begin {

        $null = Add-type -assembly "Microsoft.Office.Interop.Outlook"
        # unused variable ? $olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]
        $outlook = new-object -comobject outlook.application
        # unused variable ? $namespace = $outlook.GetNameSpace("MAPI")

        switch ($status) {
            'Free' { $olStatus = 0 }
            'Tentative' { $olStatus = 1 }
            'Busy' { $olStatus = 2 }
            'Out of Office' { $olStatus = 3 }
            'Working away' { $olStatus = 4 }
        }
    }
    process {
        #region Create New Calendar Item
        $NewEvent = $Outlook.CreateItem(1)
        $NewEvent.Subject = $Subject
        $NewEvent.Start = $Start
        $NewEvent.duration = $Duration
        $NewEvent.BusyStatus = $olStatus
        if ($NoReminder) {
            $NewEvent.ReminderSet = $false
        }
        if ($PSCmdlet.ShouldProcess("Calendar", "Creating new event $($newevent.subject)")) {
            $NewEvent.save()
        }
        #endregion
    }

    end { }

}

function New-ToDo {
    <#
    .SYNOPSIS
    Creates quick To Do list in Notepad
 
    .DESCRIPTION
    Creates quick To Do list in Notepad
 
    .PARAMETER List
    semi-colon separated list of items to put in to do list
 
    .parameter WhatIf
    [<SwitchParameter>]
    If this switch is enabled, no actions are performed but informational messages will be displayed that
    explain what would happen if the command were to run.
 
    .parameter Confirm
    [<SwitchParameter>]
    If this switch is enabled, you will be prompted for confirmation before executing any operations that
    change state.
 
    .EXAMPLE
    New-ToDo
 
    Basic execution of this function to start a new ToDo list
 
    .EXAMPLE
    New-ToDo -List "Write todo function", "Update calendar" , "Book travel", "submit expenses claim", "cook dinner"
 
    Advanced execution of this function to start a new ToDo list with specific items in the list already
 
    .NOTES
    General notes
    #>


    [cmdletbinding(SupportsShouldProcess = $true)]
    param(
        # semi-colon separated list of items to put in to do list
        [parameter(ValueFromRemainingArguments = $True)]
        [string[]]$List
    )
    # split out the items we have been sent
    $items = $List -split (';')

    # pad the ToDo items to 5 items with empty lines
    if ($items.count -lt 5) {
        $items.count..5 | ForEach-Object { $items += "" }
    }

    # set up header of list
    $txt = @"
To do list - {0:dd MMM yyyy}`r`n
"@
 -f (get-date)

    # add the items to the doc
    foreach ($Item in $items) {
        $txt += @"
[]`t$Item`r`n
"@

    }

    # add the footer (Done) section
    $txt += @"
`r`n** Done **`r`n
"@


    # create the file and display
    if ($PSCmdlet.ShouldProcess("new ToDo list file " , "Creating")) {
        $file = New-TemporaryFile
        $txt | set-content $file
        notepad $file
    }
}

function Push-PSPrompt {
    <#
    .synopsis
    Worker function that builds up the MyPrompt.ps1 file and dot sources it to apply selecgted changes
 
    .description
    This is the function that actually applies the change to the users session
 
    .example
 
    Push-PSPrompt
 
    Use Push-PSPrompt to make the customisation take effect
 
    #>


    #region build up script from components
        New-Variable -Name WorkingFolder -Value "$env:APPDATA\PSPrompt" -Option Constant
        $PromptFile = "$WorkingFolder\MyPrompt.ps1"
        ## unused variable? $ModulePath = ($env:PSModulePath -split (';'))[1]
        $mpath = (Get-Module -name psprompt)[-1].path
        $Path = Split-Path $mpath -parent
        $child = "\functions\components"
        Write-Verbose $mpath
        Write-Verbose $path
        Write-Verbose $child
        $components = (Join-Path -path $Path -ChildPath $child)
        Write-Debug "" # used as a stop line for review of variable assignment during debug
        $components = (Join-Path -path $Path -ChildPath $child -Resolve)


        # step one - the boiler-plate start of a function
        get-content "$components\_header.txt" | Out-File $PromptFile -Force

        # next read in the settings from the config file created in Set-PSPrompt
        if (!(test-path "$WorkingFolder\PSPrompt.config" )) {
            $msg = "Unable to read config file at $WorkingFolder\PSPrompt.config, check that it exists or run Set-PSPrompt. "
            Write-Warning $msg
            return
        }
        else {
            Write-Verbose "Reading settings from $WorkingFolder\PSPrompt.config"
            $PSPromptData = Import-Clixml -Path "$WorkingFolder\PSPrompt.config"
        }

        # now for each value from our hash table where True means we need to gather the script component to build up the prompt

        #region first to build is the 'second' prompt line that is shown occasionally above the prompt
        If ($PSPromptData.SecondLine) {
            # add header of Nth command
            get-content "$components\NthCommandHeader.txt" | Out-File $PromptFile -Force -Append
            # add second line content
            switch ($PSPromptData) {
                { $_.GitStatus } { get-content "$components\GitStatus.txt" | Out-File $PromptFile -Append }
            }
            # add footer of Nth command
            get-content "$components\NthCommandFooter.txt" | Out-File $PromptFile -Force -Append
        }
        #endregion

        #region - now, all the components selected to be shown in the permanent prompt line
        switch ($PSPromptData) {
            { $_.Admin } { get-content "$components\admin.txt" | Out-File $PromptFile -Append }
            { $_.Battery } { get-content "$components\battery.txt" | Out-File $PromptFile -Append }
            { $_.Day_and_date } { get-content "$components\daydate.txt" | Out-File $PromptFile -Append }
            { $_.UTC_offset } { get-content "$components\UTC_offset.txt" | Out-File $PromptFile -Append }
            { $_.last_command } { get-content "$components\last_command.txt" | Out-File $PromptFile -Append }
            { $_.shortpath } { get-content "$components\shortpath.txt" | Out-File $PromptFile -Append }
        }
        #endregion

        # complete the Prompt function boiler plate in the file so that we can dot source it dreckly
        get-content "$components\_footer.txt" | Out-File $PromptFile -Append
        write-verbose "Function compiled from components and now saved as $PromptFile"

        #region Final step is now to apply the prompt to the current session
        # dot source the prompt function to apply the changes to the prompt
        # and then add prompt function code to the profile
        try {
            Write-Verbose "Dot sourcing $Promptfile"
            . $PromptFile

            Write-Verbose "Adding prompt to CurrentUserAllHosts profile"
            $prompt = get-content $Promptfile
            $profilefile = $profile.CurrentUserAllHosts
            # check if there is a PSPROMPT function already there
            $Exists = get-content $profilefile
            if($Exists -match "PSPROMPTSTART(?s)(.*)PSPROMPTEND","jumboreplace" ){
                Write-Verbose "Existing prompt found in profile"
                $Exists -replace "PSPROMPTSTART(?s)(.*)PSPROMPTEND",$null | Set-Content $profilefile
                Write-Verbose "Previous PSPrompt found and removed from profile"
            }
            $prompt | Out-File $profilefile -Append
            write-host "`r`nCongratulations!! `r`nYour prompt and your CurrentUserAllHosts profile have been updated . If you want to change the components in effect, just run Set-PSPrompt again.
        `r`nIf you want to remove the PSPrompt changes run Set-PSPrompt -reset`r`n"

        }
        catch {
            Write-Warning "Something went wrong with applying the PSPrompt changes."
            Write-Warning "Try running <. $PromptFile>"
        }
        #endregion
    }


function Set-DisplayBrightness {
    <#
    .Synopsis
    custom function to set screen brightness
 
    .Description
    custom function to set screen brightness
     
    .parameter WhatIf
    [<SwitchParameter>]
    If this switch is enabled, no actions are performed but informational messages will be displayed that
    explain what would happen if the command were to run.
 
    .parameter Confirm
    [<SwitchParameter>]
    If this switch is enabled, you will be prompted for confirmation before executing any operations that
    change state.
 
    .Example
    Set-DisplayBrightness 50
 
    This example sets the display brightness to 50%
 
    .Example
    Set-DisplayBrightness -report
 
    This example shows the current brightness value in percent
 
    .Example
    Dim 75
 
    This example sets the brightness to 75 using the function alias
    #>

    [alias('dim')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWMICmdlet", "")]
    [cmdletbinding(SupportsShouldProcess = $true)]
    param(# supply the desired brightness as an integer from 1 to 100
        [parameter()]
        [validaterange(1, 100)]
        [int]$Brightness = 75,
        # use this parameter to get the current brightness
        [parameter()][switch]$Report
    )

    if ($Report) {
        (Get-CimInstance -Namespace root/WMI -ClassName WmiMonitorBrightness).CurrentBrightness
    }
    else {
        $display = Get-WmiObject -Namespace root\wmi -Class WmiMonitorBrightnessMethods
        # $display = Get-CimInstance -Namespace root\wmi -Class WmiMonitorBrightnessMethods # pscore requires cim
        if ($PSCmdlet.ShouldProcess("Display", "Setting brightness to $Brightness")) {
            $display.WmiSetBrightness(1, $Brightness)
        }
    }
}



function Set-PSPrompt {
    <#
    .synopsis
    The function that creates the desired prompt function.
 
    .description
    It has to build a command out of the component pieces as it will be executed everytime a command is executed and therefore cant make use of any parameterisation
 
    .parameter WhatIf
    [<SwitchParameter>]
    If this switch is enabled, no actions are performed but informational messages will be displayed that
    explain what would happen if the command were to run.
 
    .parameter Confirm
    [<SwitchParameter>]
    If this switch is enabled, you will be prompted for confirmation before executing any operations that
    change state.
 
    .example
 
    Set-PSPrompt
    
    This will start the process to configure your custom PowerShell prompt
 
    #>


    [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Custom')]

    Param(
        # [parameter(parametersetname = "default")][switch]$AddToProfile,
        # [parameter(parametersetname = "default")][switch]$Admin,
        # [parameter(parametersetname = "default")][switch]$Battery,
        # switch to reset to console prompt back to original state
        [parameter(ParameterSetName = "Reset")  ][switch]$Reset
    )
    $PSPromptData = [ordered]@{ }
    New-Variable -Name WorkingFolder -Value "$env:APPDATA\PSPrompt" -Option Constant
    $ConfigFile = "PSPrompt*.config" # TODO:: remove this line to set correct config file
    # create working folder for module if its not there
    if (!(Test-Path $WorkingFolder)) {
        $null = New-Item -Path $WorkingFolder -ItemType Directory
    }

    #region preserve the original prompt so that it can be reset if so desired
    Write-Verbose "Preserving original prompt"
    $Date = Get-Date -Format 'yyMMdd-HHmmss'
    $filename = "$WorkingFolder\prompt_$date.ps1"
    $function:prompt | Out-File -FilePath $filename
    Write-Verbose "Existing prompt written out to $filename."

    #endregion

    #region code route options:
    # 1 gather user preferences from a static json file if it exists
    # 2 user overrides the json option at (?) and wants to add/remove features
    # 3 user wants to revert to their original prompt, prior to installing PSPrompt
    # 4 user selection of prompt features at first execution

    #region Option A - Reset to original prompt
    # need to show options for reset and allow for selection

    # need to put test for $Reset ahead of other steps to avoid choosing something else before being reset
    # get-item "$WorkingFolder\prompt_*.ps1"
    # $OldPrompt =
    if ($Reset) {
        $ProfileCUAHPath = $profile.CurrentUserAllHosts
        # $ProfileCUCHPath = $profile.CurrentUserCurrentHost # unused at the moment - may look at different psprompt in different profiles oneday
        ##$ProfileCUAH = Get-Content $ProfileCUAHPath -Raw
        ##$ProfileCUCH = Get-Content $ProfileCUCHPath -Raw

        if (($ProfileCUAHPath).Length -gt 0) {
            # code $profile.CurrentUserCurrentHost
            $p = @()
            $p = get-content $ProfileCUAHPath

            if (($profile.CurrentUserAllHosts).Length -gt 0) {
                ## think this isnt needed here $p = get-content $profile.CurrentUserAllHosts
                if ($p.IndexOf("##PSPROMPTSTART##")) {
                    Write-Verbose "PSPROMPT content found in CurrentUserAllHosts"
                }
            }
            else {
                Write-Verbose "CurrentUserAllHosts profile is empty"
            }
            # locate start and end of psprompt content
            $Start = $a.IndexOf('##PSPROMPTSTART##')
            $End = $a.IndexOf('##PSPROMPTEND##')
            # identify content in profile before psprompt
            if ($start -ne 0) {
                $Before = $a[0..($Start - 1)].Clone()
            }
            else {
                $Before = $null
            }

            # identify content in profile after psprompt
            if ($End -ne ($a.Count - 1)) {
                $After = $a[($End + 1)..($a.Length)].Clone()
            }
            else {
                $After = $null
            }

            # put 'before' and 'after' content into the profile file
            $Before | Out-File -FilePath ($profile.CurrentUserAllHosts)
            $After | Out-File -FilePath ($profile.CurrentUserAllHosts) -append
        }
        return # no more processing in this script
    }
    #endregion Option A
    #region option 1 - there is config file we can load settings from

    # load the settings

    # handle multiple config files
    $ConfigFiles = get-item (join-path $WorkingFolder $configFile)
    if ($ConfigFiles.count -gt 1) {
        $i = 1
        foreach ($File in $ConfigFiles) {
            Write-Host "$i - $File"
            $i++
        }
        do {
            $LoadConfig = Read-Host "There are multiple config files - which do you want to implement?"
            Write-Host "Please select the number of the file you want to import, enter 'configure' to create a new prompt or press Ctrl + C to cancel."
        } until($LoadConfig -in ((1..$Configfiles.count), 'configure' ) )

        ## TODO - need to work out how to go to configure from here. makes me wonder if this should be step 1 ...
        $PSPromptData = Import-Clixml $configFiles[$LoadConfig - 1]
    }
    elseif ($ConfigFiles.count -lt 1) {
        Write-Warning "No config files found. Need to do something here."
    }
    else {
        $PSPromptData = Import-Clixml $configFiles
        # confirm to user
        Write-Host "There is a config file that will enable the following PSPrompt features:`r`n"
        $PSPromptData
        while ($choice -notin ('yes', 'no')) {
            $choice = Read-Host -Prompt "Do you want to apply these settings? (Yes) or (No)."
        }
        if ($choice -eq 'yes') {
            if ($PSCmdlet.ShouldProcess("Console Prompt", "Customising being applied")) {
                Push-PSPrompt $PSPromptData
            }
        }
        elseif ($choice -eq 'no') {
            Write-Verbose "Removing PSPrompt config file."
            Remove-Item $ConfigFiles
            Write-Verbose "Removing MyPrompt.ps1 config file."
            Remove-Item -path (join-path -path (split-path -path $ConfigFiles -parent) -child "myprompt.ps1")
            Write-Verbose "PSPrompt config file removed."

            return
        }
    }
    Write-Verbose "Loading from (join-path $WorkingFolder $configFiles[$LoadConfig - 1])"

    $msg = ("WorkingFolder: {0}; ConfigFiles: {1}" -f $WorkingFolder, $ConfigFiles)
    Write-Verbose $msg

    # confirm to user
    #Write-Host "There a config file that will enable the following PSPrompt features:`r`n"
    #Write-Output $PSPromptData
    #
    #$msg = "Do you want to (e)nable these features or (c)onfigure different ones?"
    #
    #do {
    # $r = Read-Host -Prompt $msg
    #}while ($r -notin ('e', 'c'))
    #
    #if ($r -eq 'e') {
    # write-host "Loading custom fprompt from $Configfiles"
    # Push-PSPrompt
    #}
    #else {
    # Remove-Item $configFiles
    #}
    # TODO:: need to add check its OK - then move to (currently) line 230
    ##Push-PSPrompt $PSPromptData


    #endregion option 1

    #region option 2 - custom choice from command line
    #endregion option 2

    # temporary check of file contents during development
    # start-process notepad "$WorkingFolder\PSPrompt.config"

    # hand over to function that reads in function sectors based on config file settings and invokes the prompt function
    #*#*#*# Push-PSPrompt $PSPromptData

    #region option 3 - reset

    #endregion option 3

    #region option 4 That means we are at first usage of Set-Prompt so we need to collect user preference
    if (!(test-path (join-path $WorkingFolder $ConfigFile))) {
        Clear-Host
        $msg = @()
        $msg += "Welcome to PSPrompt. It looks like this is your first time using the prompt so you need to make some choices on how you want your prompt to look."
        $msg += "There are some choices coming up and you need to respond with (Y)es or (N)o to each. When you respond (Yes) that feature will be included in your prompt."
        $msg += "If you respond (N)o then the feature wont be applied."
        $msg += "If you want to change your mind at any time, run the command Set-Prompt -options and you will enter this menu again."
        $msg | ForEach-Object {
            Write-Host $_
            start-sleep -Milliseconds 800
        }
        #region Admin user
        $msg = $null
        $msg += "`r`n`r`nFirst of all, do you want a notification when you are running a PS session as an Administrator level account?"
        $msg += "`r`nLike this:"
        $msg | ForEach-Object {
            Write-Host $_
            start-sleep -Milliseconds 800
        }
        Write-Host -ForegroundColor "Black" -BackgroundColor "DarkRed" "[ADMIN]"
        $r = read-host -Prompt "(Y)es to have this in the prompt, (N)o to give it a miss."
        if ($r -eq "y") {
            $PSPromptData.Adminuser = $true
        }
        else {
            $PSPromptData.Adminuser = $false

        }
        # record selection into xml file
        $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config"
        #endregion

        #region Battery
        Clear-Host
        $msg = $null
        $msg += "`r`n`r`nNext, do you work from a laptop? Do you want an indicator of your battery status while you are working on battery power?"
        $msg += "`r`nLike this:"
        $msg | ForEach-Object {
            Write-Host $_
            start-sleep -Milliseconds 800
        }
        $msg = ("[75%:60m]" )
        Write-Host -Object $msg -BackgroundColor Green -ForegroundColor Black
        $r = read-host -Prompt "(Y)es to have this in the prompt, (N)o to give it a miss."
        if ($r -eq "y") {
            $PSPromptData.Battery = $true
        }
        else {
            $PSPromptData.Battery = $false

        }
        # record selection into xml file
        $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config"
        #endregion

        #region Day and Date
        Clear-Host
        $msg = $null
        $msg += "`r`n`r`nNeed to keep your eye on the time? Add the day/date to your prompt?"
        $msg += "`r`nLike this:"
        $msg | ForEach-Object {
            Write-Host $_
            start-sleep -Milliseconds 800
        }

        $msg = "[{0}]" -f (Get-Date -Format "ddd HH:mm:ss")
        Write-Host $msg
        $r = read-host -Prompt "(Y)es to have this in the prompt, (N)o to give it a miss."
        if ($r -eq "y") {
            $PSPromptData.Day_and_date = $true
        }
        else {
            $PSPromptData.Day_and_date = $false

        }
        # record selection into xml file
        $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config"
        #endregion

        #region UTC offset
        Clear-Host
        $msg = $null
        $msg += "`r`n`r`nIf you work across timezones then as your laptop updates to a different timezone we can have your timezone offset indicated in your prompt.`r`n Add the timezone offset to your prompt?"
        $msg += "`r`nLike this:"
        $msg | ForEach-Object {
            Write-Host $_
            start-sleep -Milliseconds 800
        }

        Write-Host "`r`n(GMT +1)" -ForegroundColor Green -NoNewline
        Write-Host "`tor`t" -ForegroundColor white -NoNewline
        Write-Host "(GMT -3) `r`n" -ForegroundColor Red

        $r = read-host -Prompt "(Y)es to have this in the prompt, (N)o to give it a miss."
        if ($r -eq "y") {
            $PSPromptData.UTC_offset = $true
        }
        else {
            $PSPromptData.UTC_offset = $false
        }
        # record selection into xml file
        $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config"
        #endregion

        #region Fun function
        #endregion

        #region last command duration
        Clear-Host
        $msg = $null
        $msg += "`r`n`r`nEveryone likes to write fast executing scripts. Have the execution time of your last script shown right where you are focussed.`r`n Add the previous script duration to your prompt?"
        $msg += "`r`nLike this:"
        $msg | ForEach-Object {
            Write-Host $_
            start-sleep -Milliseconds 800
        }


        Write-Host "`r`n[56ms]" -ForegroundColor Green -NoNewline
        Write-Host "`tor`t" -ForegroundColor white -NoNewline
        Write-Host "[29s] `r`n" -ForegroundColor Red
        $r = read-host -Prompt "(Y)es to have this in the prompt, (N)o to give it a miss."
        if ($r -eq "y") {
            $PSPromptData.last_command = $true
        }
        else {
            $PSPromptData.last_command = $false
        }
        # record selection into xml file
        $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config"
        #endregion

        #region Short Path

        Clear-Host
        $msg = $null
        $msg += "`r`n`r`nSometimes you get down a lot of folder levels and the prompt gets really wide.`r`n We can give you a shortened version of the path"
        $msg += "`r`nLike this:"
        $msg | ForEach-Object {
            Write-Host $_
            start-sleep -Milliseconds 800
        }


        Write-Host "`r`nC:\...\PowerShell\ProfileF~>" -NoNewline
        Write-Host "`tinstead of `t" -ForegroundColor white -NoNewline
        Write-Host "C:\Users\jonallen\OneDrive\Scripts\PowerShell\ProfileFunctions `r`n"
        $r = read-host -Prompt "(Y)es to have this in the prompt, (N)o to give it a miss."
        if ($r -eq "y") {
            $PSPromptData.shortpath = $true
        }
        else {
            $PSPromptData.shortpath = $false
        }

        # record selection into xml file
        $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config"
        #endregion

        #region second line every Nth command
        Clear-Host
        $msg = $null
        $msg += @"
There are some things that its good to keep your eye on but you don't need to see every time you run a command.
So, we can show you some information every few commands (default is every 5 commands). Would you like a second line to occasionally give you info
about your Git status or perhaps additional battery information?
"@

        $msg += "`r`nLike this:"
        $msg | ForEach-Object {
            Write-Host $_
            start-sleep -Milliseconds 800
        }


        Write-Host "git status: " -ForegroundColor White -BackgroundColor Black -NoNewline
        Write-Host "New[0] " -ForegroundColor Green -NoNewline
        Write-Host "Mod[7] " -ForegroundColor Cyan -NoNewline
        Write-Host "Del[1]" -ForegroundColor Red
        $r = read-host -Prompt "(Y)es to have this occasionally in your prompt, (N)o to give it a miss."
        if ($r -eq "y") {
            $PSPromptData.SecondLine = $true
            $PSPromptData.GitStatus = $true
        }
        else {
            $PSPromptData.GitStatus = $false
        }
        # record selection into xml file
        $PSPromptData | Export-Clixml -Path "$WorkingFolder\PSPrompt.config"

        #endregion

        #region Battery decline rate
        # still not sure on this so leaving code in Nth command.ps1 - JA 20190503
        #endregion
    }

    #endregion (option 4)

    #endregion all options
      
}



function Show-Calendar {
    <#
 .Synopsis
  Displays a visual representation of a calendar.
 
 .Description
  Displays a visual representation of a calendar. This function supports multiple months
  and lets you highlight specific date ranges or days.
 
 .Parameter Start
  The first month to display.
 
 .Parameter End
  The last month to display.
 
 .Parameter FirstDayOfWeek
  The day of the month on which the week begins.
 
 .Parameter HighlightDay
  Specific days (numbered) to highlight. Used for date ranges like (25..31).
  Date ranges are specified by the Windows PowerShell range syntax. These dates are
  enclosed in square brackets.
 
 .Parameter HighlightDate
  Specific days (named) to highlight. These dates are surrounded by asterisks.
 
 .Example
   # Show a default display of this month.
   Show-Calendar
 
 .Example
   # Display a date range.
   Show-Calendar -Start "March, 2010" -End "May, 2010"
 
 .Example
   # Highlight a range of days.
   Show-Calendar -HighlightDay (1..10 + 22) -HighlightDate "December 25, 2008"
 
 .Example
   # Highlight a range of days.
   Show-Calendar -Start "Nov, 2010" -End "Dec, 2010" -HighlightDay (1..10 + 22) -HighlightDate "December 25, 2010"
 
 .Notes
  code sample from https://docs.microsoft.com/en-us/powershell/developer/module/how-to-write-a-powershell-script-module
 
#>

[cmdletbinding()]
    param(
        [DateTime] $start = [DateTime]::Today,
        [DateTime] $end = $start,
        $firstDayOfWeek,
        [int[]] $highlightDay,
        [string[]] $highlightDate = [DateTime]::Today #.ToString()
    )

    ## Determine the first day of the start and end months.
    $start = New-Object DateTime $start.Year, $start.Month, 1
    $end = New-Object DateTime $end.Year, $end.Month, 1

    ## Convert the highlighted dates into real dates.
    [DateTime[]]$highlightDate = [datetime[]]$highlightDate

    ## Retrieve the DateTimeFormat information so that the
    ## calendar can be manipulated.
    $dateTimeFormat = (Get-Culture).DateTimeFormat
    if ($firstDayOfWeek) {
        $dateTimeFormat.FirstDayOfWeek = $firstDayOfWeek
    }

    $currentDay = $start

    ## Process the requested months.
    while ($start -le $end) {
        ## Return to an earlier point in the function if the first day of the month
        ## is in the middle of the week.
        while ($currentDay.DayOfWeek -ne $dateTimeFormat.FirstDayOfWeek) {
            $currentDay = $currentDay.AddDays(-1)
        }

        ## Prepare to store information about this date range.
        $currentWeek = New-Object PsObject
        $dayNames = @()
        $weeks = @()

        ## Continue processing dates until the function reaches the end of the month.
        ## The function continues until the week is completed with
        ## days from the next month.
        while (($currentDay -lt $start.AddMonths(1)) -or
            ($currentDay.DayOfWeek -ne $dateTimeFormat.FirstDayOfWeek)) {
            ## Determine the day names to use to label the columns.
            $dayName = "{0:ddd}" -f $currentDay
            if ($dayNames -notcontains $dayName) {
                $dayNames += $dayName
            }

            ## Pad the day number for display, highlighting if necessary.
            $displayDay = " {0:00} " -f $currentDay.Day

            ## Determine whether to highlight a specific date.
            if ($highlightDate) {
                $compareDate = New-Object DateTime $currentDay.Year, $currentDay.Month, $currentDay.Day
                if ($highlightDate -contains $compareDate) {
                    $displayDay = "*" + ("{0:00}" -f $currentDay.Day) + "*"
                }
            }

            ## Otherwise, highlight as part of a date range.
            if ($highlightDay -and ($highlightDay[0] -eq $currentDay.Day)) {
                $displayDay = "[" + ("{0:00}" -f $currentDay.Day) + "]"
                $null, $highlightDay = $highlightDay
            }

            ## Add the day of the week and the day of the month as note properties.
            $currentWeek | Add-Member NoteProperty $dayName $displayDay

            ## Move to the next day of the month.
            $currentDay = $currentDay.AddDays(1)

            ## If the function reaches the next week, store the current week
            ## in the week list and continue.
            if ($currentDay.DayOfWeek -eq $dateTimeFormat.FirstDayOfWeek) {
                $weeks += $currentWeek
                $currentWeek = New-Object PsObject
            }
        }

        ## Format the weeks as a table.
        $calendar = $weeks | Format-Table $dayNames -AutoSize | Out-String

        ## Add a centered header.
        $width = ($calendar.Split("`n") | Measure-Object -Maximum Length).Maximum
        $header = "{0:MMMM yyyy}" -f $start
        $padding = " " * (($width - $header.Length) / 2)
        $displayCalendar = " `n" + $padding + $header + "`n " + $calendar
        $displayCalendar.TrimEnd()

        ## Move to the next month.
        $start = $start.AddMonths(1)
    }
}
#Export-ModuleMember -Function Show-Calendar
#endregion Load compiled code