MKPowerShell.psm1

$RegistryKey = 'HKCU:\SOFTWARE\MKPowerShell'
$MKPowerShellAppData = "$Env:LOCALAPPDATA\MKPowerShell"

<#
.SYNOPSIS
Concatnates PowerShell histories, so that you can reference previous commands from previous sessions.

.DESCRIPTION
When PowerShell starts, it will load the previous CSV file (via Import-Csv) and concatnate (via Add-History) it to current session. Doing this allows you to reference previous command from any previous session.

.INPUTS
None

.OUTPUTS
None

.EXAMPLE
E:\> Get-History
E:\> Invoke-History
#>

function Export-History {
    [CmdletBinding(PositionalBinding = $True)]
    Param()

    if ((Test-Path $MKPowerShellAppData -ErrorAction SilentlyContinue) -eq $False ) {
        New-Item -Path $MKPowerShellAppData -ItemType Directory
        New-Item -Path "$MKPowerShellAppData\SessionHistories.csv" -ItemType File
        
        $Script:SessionHistoriesCount = 0
    }
    # TODO: check $MaximumHistoryCount
    $SessionHistory = Get-History
    $EntriesToExport = [math]::Abs($Script:SessionHistories.Count - $SessionHistory.Count)
    
    Get-History -Count $EntriesToExport | Export-Csv -Path "$MKPowerShellAppData\SessionHistories.csv" 
}

function Import-History {
    [CmdletBinding(PositionalBinding = $True)]
    Param()

    if ((Test-Path $MKPowerShellAppData -ErrorAction SilentlyContinue) -eq $False ) {
        New-Item -Path $MKPowerShellAppData -ItemType Directory
        New-Item -Path "$MKPowerShellAppData\SessionHistories.csv" -ItemType File

        $Script:SessionHistoriesCount = 0
    }
    else {
        $SessionHistories = Import-Csv -Path "$MKPowerShellAppData\SessionHistories.csv"
        $Script:SessionHistoriesCount = $SessionHistories.Count;

        $SessionHistories | Add-History
    }
}

<#
.SYNOPSIS
Concatnates PowerShell histories, so that you can reference previous commands from previous sessions.

.DESCRIPTION
Displays history in descending order

.INPUTS
None

.OUTPUTS
None

.EXAMPLE
E:\> Show-History

ExecutionTime CommandLine Id
------------- ----------- --
Saturday, April 7, 2018 3:52:21 PM exit 62
Saturday, April 7, 2018 3:52:05 PM Show-History 61
Saturday, April 7, 2018 3:42:59 PM sl.. 60
Saturday, April 7, 2018 3:42:29 PM Get-Content config.xml 59
...
#>

function Show-History {
    [CmdletBinding(PositionalBinding = $False)]
    Param()

    Get-History | Sort-Object -Descending Id | `
        Format-Table @{Label = "ExecutionTime"; Expression = {($_.EndExecutionTime.DateTime)}; Alignment = 'Left'}, `
    @{Label = "CommandLine"; Expression = {($_.CommandLine)}; Alignment = 'Right'}, `
    @{Label = "Id"; Expression = {($_.Id)}; Alignment = 'Left'} -AutoSize
}

<#
.SYNOPSIS
Lists all available functions for a module, with the synopsis of the functions.

.DESCRIPTION
Lists all available functions of a module using Get-Command and Get-Help.

.INPUTS
None

.OUTPUTS
PSCustomObject

.EXAMPLE
E:\> Get-ModuleSynopsis Microsoft.PowerShell.Utility

Name Synopsis
---- --------
ConvertFrom-SddlString
Format-Hex Displays a file or other input as hexadecimal.
Get-FileHash Computes the hash value for a file by using a specified hash algorithm.
Import-PowerShellDataFile
New-Guid Creates a GUID.
New-TemporaryFile Creates a temporary file.
Add-Member Adds custom properties and methods to an instance of a Windows PowerShell object.
Add-Type Adds a.NET Framework type (a class) to a Windows PowerShell session.
#>

function Get-ModuleSynopsis {
    [CmdletBinding(PositionalBinding = $False)]
    Param(
        # Any other parameters can go here
    )
 
    DynamicParam {
        # Set the dynamic parameters' name
        $ParameterName = 'Name'
            
        # Create the dictionary
        $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary

        # Create the collection of attributes
        $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            
        # Create and set the parameters' attributes
        $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
        $ParameterAttribute.Mandatory = $True
        $ParameterAttribute.Position = 0

        # Add the attributes to the attributes collection
        $AttributeCollection.Add($ParameterAttribute)

        # Generate and set the ValidateSets
        $NonInstalledSet = Get-Module | Select-Object -ExpandProperty Name
        $InstalledSet = Get-InstalledModule | Select-Object -ExpandProperty Name
            
        $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($NonInstalledSet + $InstalledSet)

        # Add the ValidateSet to the attributes collection
        $AttributeCollection.Add($ValidateSetAttribute)

        # Create and return the dynamic parameter
        $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
        $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
        return $RuntimeParameterDictionary
    }

    begin {
        # Bind the parameter to a friendly variable
        $Name = $PsBoundParameters[$ParameterName]
    }

    process {
        Get-Command -Module $Name | Foreach-Object {
            $Syno = Get-Help -Name $_.Name | Select-Object -ExpandProperty Synopsis

            [PSCustomObject]@{
                Name     = $_.Name
                Synopsis = $Syno 
            }
        }
    }
}

<#
.SYNOPSIS
Will backup profile to desired location when PowerShell starts

.DESCRIPTION
Upon PowerShell startup, profile will be copied to the value given to this function

.INPUTS
None

.OUTPUTS
None

.EXAMPLE
E:\projects> Set-BackupProfileLocation 'D:\Google Drive\Documents\PowerShell'

#>

function Set-BackupProfileLocation {
    [CmdletBinding(PositionalBinding = $True)]
    Param
    (
        [Parameter(Mandatory = $True, Position = 1)]
        [string]$Value
    )

    Set-ItemProperty -Path $RegistryKey -Name BackupProfileLocation -Value $Value
}

# backup this profile to Google Drive in case if working on new computer or this
# computer gets reformatted which might be forgotten.
function Backup-PowerShellProfile {
    [CmdletBinding(PositionalBinding = $False)]
    Param
    (
        [Parameter(Mandatory = $False)]
        [string]$Destination
    )

    if (-not $Destination) {
        try {
            if ( $(Test-Path $RegistryKey -ErrorAction SilentlyContinue) -eq $True ) {

                $Destination = Get-ItemPropertyValue -Path $RegistryKey -Name BackupProfileLocation

                if ((Test-Path $Destination -PathType Container) -eq $False) {
                    New-Item $Destination -ItemType Directory
                }
                Copy-Item -Path $PROFILE -Destination $Destination -Force

                #Write-Host "BackupProfileLocation has been updated"
            }
        }
        catch {
            #Write-Host "No BackupProfileLocation value has been set."
        }
    }
}


<#
.SYNOPSIS
Updates other PowerShell profiles with Microsoft.PowerShell_profile.ps1

.DESCRIPTION
Currently this is hard-coded to only update VSCode profile. Obviously this will need to be changed
to live up to its name.

.INPUTS
None

.OUTPUTS
None

.EXAMPLE

.NOTES
$args is being used here for for Register-ObjectEvent scope
#>

function Update-PowerShellProfile {
    [CmdletBinding(PositionalBinding = $False)]
    Param
    (
        [Parameter(Mandatory = $False)]
        [string]$Path = "$args\Microsoft.VSCode_profile.ps1",

        [Parameter(Mandatory = $False)]
        [string[]]$Include
    )

    # overwrite $Path with the following...
    $VSCode_Profile_Mesg = '# THIS FILE IS AUTOGENERATED THAT IS OVERWRITTEN BY Microsoft.PowerShell_profile.ps1 FILE'
    Set-Content $Path -Value $VSCode_Profile_Mesg  -Force | Out-Null

    # include only...
    Get-Content $PROFILE | `
        ForEach-Object {
        if ($_.StartsWith('Import-Module')) {
            $_
        }
    } | Add-Content $Path -Force

    # append the following...
    Add-Content $Path -Value @"

# overwrite alias 'sl' with MKPowerShell module's Set-LocationAndStore
Set-Alias sl Set-LocationAndStore -Force
"@
 -Force

}

<#
.SYNOPSIS
Restarts PowerShell

.DESCRIPTION
Restarts PowerShell

# .ALIAS
pwsh

.INPUTS
None

.OUTPUTS
None

.EXAMPLE
E:\projects> pwsh

.LINK
Restart-PWSHAdmin
#>

function Restart-PWSH {
    [CmdletBinding(PositionalBinding = $False)]
    Param()
    Unregister-Event -SourceIdentifier PowerShell.Exiting -ErrorAction SilentlyContinue
    Register-EngineEvent -SourceIdentifier PowerShell.Exiting -SupportEvent -Action {
        Export-History
        Start-Process -FilePath "pwsh.exe" -Verb open
    } | Out-Null
    exit
}
Set-Alias pwsh Restart-PWSH -Scope Global
Write-Host "'pwsh' alias is now mapped to 'Restart-PWSH'."


<#
.SYNOPSIS
Restarts PowerShell with Administrator privileges

.DESCRIPTION
Restarts PowerShell with Administrator privileges

# .ALIAS
pwsha

.INPUTS
None

.OUTPUTS
None

.EXAMPLE
E:\projects> pwsha

.LINK
Restart-PWSH
#>

function Restart-PWSHAdmin {
    [CmdletBinding(PositionalBinding = $False)]
    Param()
    Unregister-Event -SourceIdentifier PowerShell.Exiting -ErrorAction SilentlyContinue
    Register-EngineEvent -SourceIdentifier PowerShell.Exiting -SupportEvent -Action {
        Export-History
        Start-Process -FilePath "pwsh.exe" -Verb runAs
    } | Out-Null
    exit
}
Set-Alias pwsha Restart-PWSHAdmin -Scope Global
Write-Host "'pwsha' alias is now mapped to 'Restart-PWSHAdmin'."

<#
.SYNOPSIS Streamline publishing module to PowerShellGet.

.DESCRIPTION Prior to calling you can store API key using Set-NuGetApiKey. If not, you must assign it to the NuGetApiKey parameter. When called this function will take the directory (or file's directory) and will copy it to the PowerShell module directory (eg: C:\Users\Marc\Documents\PowerShell\Modules) where PowerShell can publish it to an online gallery.

.INPUTS None

.OUTPUTS None

.EXAMPLE
E:\projects\MKPowerShell> Set-NuGetApiKey 'a1b2c3d4-e5f6-g7h8-i9j1-0k11l12m13n1'
E:\projects\MKPowerShell> Publish-Module

.LINK Set-NuGetApiKey
#>

function Publish-Module {
    [CmdletBinding(PositionalBinding = $True)]
    Param
    (
        [Parameter(Mandatory = $False)]
        [string]$Path = (Get-Location | Select-Object -ExpandProperty Path),

        [Parameter(Mandatory = $False)]
        [string]$NuGetApiKey = (Get-ItemPropertyValue -Path $RegistryKey -Name NuGetApiKey),

        [Parameter(Mandatory = $False)]
        [string[]]$Exclude = ('.git', '.vscode', '.gitignore'),

        [switch]$WhatIf
    )
    
    # ignore .psd1, just concerned about module's root directory
    if ((Test-Path -Path $Path -PathType Container) -eq $False) {
        $Path = Split-Path -Path $Path -Parent -Resolve
    }

    # pick the first directory of $Env:PSModulePath and add the module's root directory name to it
    $ModuleDirectoryName = (Split-Path -Path $Path -Leaf)
    $DestinationDirectory = Join-Path -Path ($Env:PSModulePath.Split(';')[0]) -ChildPath $ModuleDirectoryName

    # if it exists in $Env:PSModulePath[0], remove it
    if ((Test-Path -Path $DestinationDirectory -PathType Container) -eq $True) {
        Remove-Item $DestinationDirectory -Recurse -Force -Verbose:$($Verbose.IsPresent -or $WhatIf.IsPresent)
    }
    
    # setup deploy directory
    New-Item $DestinationDirectory -ItemType Directory -Verbose:$($Verbose.IsPresent -or $WhatIf.IsPresent)| `
        Out-Null
    
    # setup deploy directory
    Get-ChildItem -Path $Path -Exclude $Exclude -Recurse | `
        Copy-Item -Destination $DestinationDirectory -Verbose:$($Verbose.IsPresent -or $WhatIf.IsPresent)

    PowerShellGet\Publish-Module -Name $DestinationDirectory -NuGetApiKey $NuGetApiKey -Verbose -Confirm:$(-not $WhatIf.IsPresent) -WhatIf:$WhatIf.IsPresent
    
    Write-Information "Will be using the following value for NuGet API Key: $NuGetApiKey" -InformationAction Continue
    
    # teardown
    Remove-Item $DestinationDirectory -Recurse -Force -Verbose:$($Verbose.IsPresent -or $WhatIf.IsPresent)
}

<#
.SYNOPSIS
Stores NuGet API key to be used with Publish-Module

.DESCRIPTION
Stores NuGet API key in the registry so that when Publish-Module is called it will retrieve the key without promting you for it.

.INPUTS
None

.OUTPUTS
None

.EXAMPLE
E:\projects\MKPowerShell> Set-NuGetApiKey 'a1b2c3d4-e5f6-g7h8-i9j1-0k11l12m13n1'
E:\projects\MKPowerShell> Publish-Module

.LINK
Publish-Module
#>

function Set-NuGetApiKey {
    [CmdletBinding(PositionalBinding = $True)]
    Param
    (
        [Parameter(Mandatory = $True, Position = 1)]
        [string]$Value
    )

    Set-ItemProperty -Path $RegistryKey -Name NuGetApiKey -Value $Value
}

<#
.SYNOPSIS
Stores last location and restores that location when PowerShell restarts

.DESCRIPTION
Stores last value of and restores that location when PowerShell restarts so that it continues in the directory you last were in previous session.

# .ALIAS
sl

.INPUTS
None

.OUTPUTS
System.Management.Automation.PathInfo, System.Management.Automation.PathInfoStack

.EXAMPLE
E:\> sl projects
E:\projects> sl..
#>

function Set-LocationAndStore {
    [CmdletBinding(PositionalBinding = $True)]
    Param
    (
        [Parameter(Mandatory = $False, Position = 1)]
        [string]$Path,

        [Parameter(Mandatory = $False)]
        [string]$LiteralPath,

        [switch]$PassThru
    )

    if ($Path) {
        Set-Location -Path $Path
    }
    else {
        Set-Location -LiteralPath $LiteralPath
    }

    Set-ItemProperty -Path $RegistryKey -Name LastLocation -Value (Get-Location) -PassThru:$PassThru.IsPresent
}
Set-Alias sl Set-LocationAndStore -Scope Global -Force
Write-Host "'sl' alias is now mapped to 'Set-LocationAndStore'."

function Restore-RegistrySettings {
    [CmdletBinding(PositionalBinding = $False)]
    Param()

    if ( $(Test-Path $RegistryKey -ErrorAction SilentlyContinue) -eq $True ) {

        $LastLocation = Get-ItemPropertyValue -Path $RegistryKey -Name LastLocation

        if ( $(Test-Path $LastLocation -ErrorAction SilentlyContinue) -eq $True ) {
            Set-Location $LastLocation

            Write-Host "Restored LastLocation"
        }
    }
    else {
        $InitalLocation = Get-Location

        Push-Location

        Set-Location -Path 'HKCU:\SOFTWARE\'
        
        $RegKey = New-Item -Name 'MKPowerShell'
        $RegKey | New-ItemProperty -Name LastLocation -Value $InitalLocation -PropertyType String
        $RegKey | New-ItemProperty -Name NuGetApiKey -Value '' -PropertyType String
        $RegKey | New-ItemProperty -Name BackupProfileLocation -Value '' -PropertyType String

        Pop-Location
        
        Write-Host "New registry key for MKPowerShell has been created."
    }
}

function Start-PowerShellSession {
    [CmdletBinding(PositionalBinding = $False)]
    Param()
    
    Register-Shutdown
    
    Restore-RegistrySettings
    Import-History
    
    Register-PostStartUp
}

function Register-PostStartUp {
    [CmdletBinding(PositionalBinding = $False)]
    Param()
   
    $job = Start-Job { Start-Sleep -Seconds 60 }
    
    Register-ObjectEvent $job -EventName StateChanged -SourceIdentifier StartUpJobEnd -Action {
        if ($sender.State -eq 'Completed') {
            Backup-PowerShellProfile
            Update-PowerShellProfile

            Unregister-Event StartUpJobEnd
            Remove-Job $job
        } 
    } | Out-Null
}

function Register-Shutdown {
    [CmdletBinding(PositionalBinding = $False)]
    Param()

    Register-EngineEvent -SourceIdentifier PowerShell.Exiting -SupportEvent -Action {
        Export-History
    } | Out-Null
}  

Start-PowerShellSession