PWSHModule.psm1

#region Public Functions
#region Add-PWSHModule.ps1
######## Function 1 of 12 ##################
# Function: Add-PWSHModule
# Module: PWSHModule
# ModuleVersion: 0.1.21
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/07/09 15:57:31
# ModifiedOn: 2022/09/08 00:32:01
# Synopsis: Adds a new module to the GitHub Gist List.
#############################################
 
<#
.SYNOPSIS
Adds a new module to the GitHub Gist List.
 
.DESCRIPTION
Adds a new module to the GitHub Gist List.
 
.PARAMETER ListName
The File Name on GitHub Gist.
 
.PARAMETER ModuleName
Name of the module to add. You can also use a keyword to search for.
 
.PARAMETER Repository
Name of the Repository to hosting the module.
 
.PARAMETER RequiredVersion
This will force a version to be used. Leave blank to use the latest version.
 
.PARAMETER GitHubUserID
The GitHub User ID.
 
.PARAMETER GitHubToken
GitHub Token with access to the Users' Gist.
 
.EXAMPLE
Add-PWSHModule -ListName base -ModuleName pslauncher -Repository PSgallery -RequiredVersion 0.1.19 -GitHubUserID smitpi -GitHubToken $GitHubToken
 
#>

Function Add-PWSHModule {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PWSHModule/Add-PWSHModule')]
    PARAM(
        [Parameter(Mandatory)]
        [string[]]$ListName,
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Name')]
        [string[]]$ModuleName,
        [String]$Repository = 'PSGallery',
        [string]$RequiredVersion,
        [Parameter(Mandatory = $true)]
        [string]$GitHubUserID,
        [Parameter(Mandatory = $true)]
        [string]$GitHubToken
    )

    begin {
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) BEGIN] Starting $($myinvocation.mycommand)"
            $headers = @{}
            $auth = '{0}:{1}' -f $GitHubUserID, $GitHubToken
            $bytes = [System.Text.Encoding]::ASCII.GetBytes($auth)
            $base64 = [System.Convert]::ToBase64String($bytes)
            $headers.Authorization = 'Basic {0}' -f $base64

            Write-Verbose "[$(Get-Date -Format HH:mm:ss) Starting connect to github"
            $url = 'https://api.github.com/users/{0}/gists' -f $GitHubUserID
            $AllGist = Invoke-RestMethod -Uri $url -Method Get -Headers $headers -ErrorAction Stop
            $PRGist = $AllGist | Select-Object | Where-Object { $_.description -like 'PWSHModule-ConfigFile' }
        } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}
        [System.Collections.generic.List[PSObject]]$NewModuleObject = @()
    }
    process {
        foreach ($ModName in $ModuleName) {
            Write-Host '[Searching]' -NoNewline -ForegroundColor Yellow; Write-Host ' for Module: ' -NoNewline -ForegroundColor Cyan; Write-Host "$($ModName)" -ForegroundColor Green
            $index = 0
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Finding modules"
            $FilterMod = Find-Module -Filter $ModName -Repository $Repository | ForEach-Object {
                [PSCustomObject]@{
                    Index       = $index
                    Name        = $_.Name
                    Version     = $_.version
                    Description = $_.Description
                    ProjectURI  = $_.ProjectUri.AbsoluteUri
                }
                $index++
            }

            if ($filtermod.name.count -gt 1) {
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] More than one module found"
                $FilterMod | Select-Object Index, Name, Description | Format-Table -AutoSize -Wrap
                $num = Read-Host 'Index Number '
                $ModuleToAdd = $filtermod[$num]
            } elseif ($filtermod.name.Count -eq 1) {
                $ModuleToAdd = $filtermod
            } else {Write-Error 'Module not found'}

            if ($RequiredVersion) {
                try {
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Looking for versions"
                    Find-Module -Name $ModuleToAdd.name -RequiredVersion $RequiredVersion -Repository $Repository -ErrorAction Stop | Out-Null
                    $VersionToAdd = $RequiredVersion
                } catch {
                    $index = 0
                    Find-Module -Name $ModuleToAdd.name -AllVersions -Repository $Repository | ForEach-Object {
                        [PSCustomObject]@{
                            Index   = $index
                            Version = $_.Version
                        }
                        $index++
                    } | Tee-Object -Variable Version | Format-Table
                    $versionnum = Read-Host 'Index Number '
                    $VersionToAdd = $version[$versionnum].Version
                }
            } else {$VersionToAdd = 'Latest'}

            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Create new Object"
            $NewModuleObject.Add([PSCustomObject]@{
                    Name        = $ModuleToAdd.Name
                    Version     = $VersionToAdd
                    Description = $ModuleToAdd.Description
                    Repository  = $Repository
                    Projecturi  = $ModuleToAdd.ProjectUri
                })
                
        }
    }
    end {
        foreach ($List in $ListName) {
            try {
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) Checking Config File"
                $Content = (Invoke-WebRequest -Uri ($PRGist.files.$($List)).raw_url -Headers $headers).content | ConvertFrom-Json -ErrorAction Stop
            } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
            if ([string]::IsNullOrEmpty($Content.CreateDate) -or [string]::IsNullOrEmpty($Content.Modules)) {Write-Error 'Invalid Config File'}

            [System.Collections.generic.List[PSObject]]$ModuleObject = @()
            $Content.Modules | ForEach-Object {$ModuleObject.Add($_)}
            
            $NewModuleObject | ForEach-Object {
                if ($_.name -notin $ModuleObject.Name) {
                    $ModuleObject.Add($_)
                    Write-Host '[Added]' -NoNewline -ForegroundColor Yellow; Write-Host " $($_.Name)" -NoNewline -ForegroundColor Cyan; Write-Host " to List: $($List)" -ForegroundColor Green
                } else {Write-Host '[Duplicate]' -NoNewline -ForegroundColor red; Write-Host " $($_.Name)" -NoNewline -ForegroundColor Cyan; Write-Host " to List: $($List)" -ForegroundColor Green}
            }

            $Content.Modules = $ModuleObject | Sort-Object -Property name
            $Content.ModifiedDate = "$(Get-Date -Format u)"
            $content.ModifiedUser = "$($env:USERNAME.ToLower())"
            try {
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Uploading to gist"
                $Body = @{}
                $files = @{}
                $Files["$($PRGist.files.$($List).Filename)"] = @{content = ( $Content | ConvertTo-Json | Out-String ) }
                $Body.files = $Files
                $Uri = 'https://api.github.com/gists/{0}' -f $PRGist.id
                $json = ConvertTo-Json -InputObject $Body
                $json = [System.Text.Encoding]::UTF8.GetBytes($json)
                $null = Invoke-WebRequest -Headers $headers -Uri $Uri -Method Patch -Body $json -ErrorAction Stop
                Write-Host '[Uploaded]' -NoNewline -ForegroundColor Yellow; Write-Host " List: $($List)" -NoNewline -ForegroundColor Cyan; Write-Host ' to Github Gist' -ForegroundColor Green
            } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) DONE]"
        }
    }
} #end Function


$scriptblock = {
    (Get-PSRepository).Name
}
Register-ArgumentCompleter -CommandName Add-PWSHModule -ParameterName Repository -ScriptBlock $scriptBlock


$scriptblock2 = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if ([bool]($PSDefaultParameterValues.Keys -like '*:GitHubUserID')) {(Get-PWSHModuleList).name | Where-Object {$_ -like "*$wordToComplete*"}}
}
Register-ArgumentCompleter -CommandName Add-PWSHModule -ParameterName ListName -ScriptBlock $scriptBlock2
 
Export-ModuleMember -Function Add-PWSHModule
#endregion
 
#region Add-PWSHModuleDefaultsToProfile.ps1
######## Function 2 of 12 ##################
# Function: Add-PWSHModuleDefaultsToProfile
# Module: PWSHModule
# ModuleVersion: 0.1.21
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/07/31 11:51:50
# ModifiedOn: 2022/09/02 13:00:37
# Synopsis: Creates PSDefaultParameterValues in the users profile files.
#############################################
 
<#
.SYNOPSIS
Creates PSDefaultParameterValues in the users profile files.
 
.DESCRIPTION
Creates PSDefaultParameterValues in the users profile files.
 
.PARAMETER GitHubUserID
The GitHub User ID.
 
.PARAMETER PublicGist
Select if the list is hosted publicly.
 
.PARAMETER GitHubToken
GitHub Token with access to the Users' Gist.
 
.PARAMETER Scope
Where the module will be installed. AllUsers require admin access.
 
.PARAMETER RemoveConfig
Removes the config from your profile.
 
.EXAMPLE
Add-PWSHModuleDefaultsToProfile -GitHubUserID smitpi -PublicGist -Scope AllUsers
 
#>

Function Add-PWSHModuleDefaultsToProfile {
    [Cmdletbinding(DefaultParameterSetName = 'Public', HelpURI = 'https://smitpi.github.io/PWSHModule/Add-PWSHModuleDefaultsToProfile')]
    [OutputType([System.Object[]])]
    PARAM(
        [Parameter(Mandatory)]
        [string]$GitHubUserID, 
        [Parameter(ParameterSetName = 'Public')]
        [switch]$PublicGist,
        [Parameter(ParameterSetName = 'Private')]
        [string]$GitHubToken,
        [ValidateSet('AllUsers', 'CurrentUser')]
        [string]$Scope,
        [switch]$RemoveConfig
    )

    ## TODO Add remove config from profile.

    if ($PublicGist) {
        $Script:PSDefaultParameterValues['*PWSHModule*:GitHubUserID'] = "$($GitHubUserID)"
        $Script:PSDefaultParameterValues['*PWSHModule*:PublicGist'] = $true
        $Script:PSDefaultParameterValues['*PWSHModule*:Scope'] = "$($Scope)"

        $ToAppend = @"
 
#region PWSHModule Defaults
`$PSDefaultParameterValues['*PWSHModule*:GitHubUserID'] = "$($GitHubUserID)"
`$PSDefaultParameterValues['*PWSHModule*:PublicGist'] = `$true
`$PSDefaultParameterValues['*PWSHModule*:Scope'] = "$($Scope)"
#endregion PWSHModule
"@

    } else {
        $Script:PSDefaultParameterValues['*PWSHModule*:GitHubUserID'] = "$($GitHubUserID)"
        $Script:PSDefaultParameterValues['*PWSHModule*:GitHubToken'] = "$($GitHubToken)"
        $Script:PSDefaultParameterValues['*PWSHModule*:Scope'] = "$($Scope)"
        $ToAppend = @"
         
#region PWSHModule Defaults
`$PSDefaultParameterValues['*PWSHModule*:GitHubUserID'] = "$($GitHubUserID)"
`$PSDefaultParameterValues['*PWSHModule*:GitHubToken'] = "$($GitHubToken)"
`$PSDefaultParameterValues['*PWSHModule*:Scope'] = "$($Scope)"
#endregion PWSHModule
"@

    }

    try {
        $CheckProfile = Get-Item $PROFILE -ErrorAction Stop
    } catch { $CheckProfile = New-Item $PROFILE -ItemType File -Force}
    
    $Files = Get-ChildItem -Path "$($CheckProfile.Directory)\*profile*"
    foreach ($file in $files) {    
        $tmp = Get-Content -Path $file.FullName | Where-Object { $_ -notlike '*PWSHModule*'}
        $tmp | Set-Content -Path $file.FullName -Force
        if (-not($RemoveConfig)) {Add-Content -Value $ToAppend -Path $file.FullName -Force -Encoding utf8}
        Write-Host '[Updated]' -NoNewline -ForegroundColor Yellow; Write-Host ' Profile File:' -NoNewline -ForegroundColor Cyan; Write-Host " $($file.FullName)" -ForegroundColor Green
    }

} #end Function
 
Export-ModuleMember -Function Add-PWSHModuleDefaultsToProfile
#endregion
 
#region Get-PWSHModuleList.ps1
######## Function 3 of 12 ##################
# Function: Get-PWSHModuleList
# Module: PWSHModule
# ModuleVersion: 0.1.21
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/07/13 01:15:39
# ModifiedOn: 2022/09/07 17:34:02
# Synopsis: List all the GitHub Gist Lists.
#############################################
 
<#
.SYNOPSIS
List all the GitHub Gist Lists.
 
.DESCRIPTION
List all the GitHub Gist Lists.
 
.PARAMETER GitHubUserID
The GitHub User ID.
 
.PARAMETER PublicGist
Select if the list is hosted publicly.
 
.PARAMETER GitHubToken
GitHub Token with access to the Users' Gist.
 
.EXAMPLE
Get-PWSHModuleList -GitHubUserID smitpi -GitHubToken $GitHubToken
 
#>

Function Get-PWSHModuleList {
    [Cmdletbinding(DefaultParameterSetName = 'Private', HelpURI = 'https://smitpi.github.io/PWSHModule/Get-PWSHModuleList')]
    [Alias ('Show-PWSHModuleList')]
    PARAM(
        [Parameter(Mandatory)]
        [string]$GitHubUserID, 
        [Parameter(ParameterSetName = 'Public')]
        [switch]$PublicGist,
        [Parameter(ParameterSetName = 'Private')]
        [string]$GitHubToken
    )

    
    try {
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Connect to gist"
        $headers = @{}
        $auth = '{0}:{1}' -f $GitHubUserID, $GitHubToken
        $bytes = [System.Text.Encoding]::ASCII.GetBytes($auth)
        $base64 = [System.Convert]::ToBase64String($bytes)
        $headers.Authorization = 'Basic {0}' -f $base64

        $url = 'https://api.github.com/users/{0}/gists' -f $GitHubUserID
        $AllGist = Invoke-RestMethod -Uri $url -Method Get -Headers $headers -ErrorAction Stop
        $PRGist = $AllGist | Select-Object | Where-Object { $_.description -like 'PWSHModule-ConfigFile' }
    } catch {throw "Can't connect to gist:`n $($_.Exception.Message)"}


    [System.Collections.ArrayList]$GistObject = @()
    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Create object"
    $PRGist.files | Get-Member -MemberType NoteProperty | ForEach-Object {
        $Content = (Invoke-WebRequest -Uri ($PRGist.files.$($_.name)).raw_url -Headers $headers).content | ConvertFrom-Json -ErrorAction Stop
        if ($Content.modifiedDate -notlike 'Unknown') {
            $modifiedDate = [datetime]$Content.ModifiedDate
            $modifiedUser = $Content.ModifiedUser
        } else { 
            $modifiedDate = 'Unknown'
            $modifiedUser = 'Unknown'
        }
        [void]$GistObject.Add([PSCustomObject]@{
                Name         = $_.Name
                Description  = $Content.Description
                Date         = [datetime]$Content.CreateDate
                Author       = $Content.Author
                ModifiedDate = $modifiedDate
                ModifiedUser = $modifiedUser
            })
    }

    $GistObject
    Write-Verbose "[$(Get-Date -Format HH:mm:ss) DONE]"

} #end Function
 
Export-ModuleMember -Function Get-PWSHModuleList
#endregion
 
#region Install-PWSHModule.ps1
######## Function 4 of 12 ##################
# Function: Install-PWSHModule
# Module: PWSHModule
# ModuleVersion: 0.1.21
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/07/12 07:38:48
# ModifiedOn: 2022/09/07 23:36:35
# Synopsis: Install modules from the specified list.
#############################################
 
<#
.SYNOPSIS
Install modules from the specified list.
 
.DESCRIPTION
Install modules from the specified list.
 
.PARAMETER ListName
The File Name on GitHub Gist.
 
.PARAMETER Scope
Where the module will be installed. AllUsers require admin access.
 
.PARAMETER AllowPrerelease
Allow the installation on beta modules.
 
.PARAMETER GitHubUserID
The GitHub User ID.
 
.PARAMETER PublicGist
Select if the list is hosted publicly.
 
.PARAMETER GitHubToken
GitHub Token with access to the Users' Gist.
 
.PARAMETER LocalList
Select if the list is saved locally.
 
.PARAMETER Path
Directory where files are saved.
 
.EXAMPLE
Install-PWSHModule -Filename extended -Scope CurrentUser -GitHubUserID smitpi -GitHubToken $GitHubToken
 
#>

Function Install-PWSHModule {
    [Cmdletbinding(DefaultParameterSetName = 'Private', HelpURI = 'https://smitpi.github.io/PWSHModule/Install-PWSHModule')]
    PARAM(
        [Parameter(Position = 0)]
        [string[]]$ListName,
        [Parameter(Position = 1)]
        [ValidateSet('AllUsers', 'CurrentUser')]
        [string]$Scope,
        [switch]$AllowPrerelease,
        [Parameter(Mandatory, ParameterSetName = 'Public')]
        [Parameter(Mandatory, ParameterSetName = 'Private')]
        [string]$GitHubUserID,
        [Parameter(ParameterSetName = 'Public')]
        [switch]$PublicGist,
        [Parameter(ParameterSetName = 'Private')]
        [string]$GitHubToken,
        [Parameter(ParameterSetName = 'local')]
        [switch]$LocalList,
        [Parameter(ParameterSetName = 'local')]
        [System.IO.DirectoryInfo]$Path
    )

    if ($scope -like 'AllUsers') {
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) BEGIN] Check for admin"
     $IsAdmin = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
        if (-not($IsAdmin.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))) { Write-Error 'Must be running an elevated prompt.' }
    }

    if ($GitHubUserID) {
        try {
            if ($PublicGist) {
                Write-Host '[Using] ' -NoNewline -ForegroundColor Yellow 
                Write-Host 'Public Gist:' -NoNewline -ForegroundColor Cyan 
                Write-Host ' for list:' -ForegroundColor Green -NoNewline 
                Write-Host "$($ListName)" -ForegroundColor Cyan
            }

            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Connect to Gist"
            $headers = @{}
            $auth = '{0}:{1}' -f $GitHubUserID, $GitHubToken
            $bytes = [System.Text.Encoding]::ASCII.GetBytes($auth)
            $base64 = [System.Convert]::ToBase64String($bytes)
            $headers.Authorization = 'Basic {0}' -f $base64

            $url = 'https://api.github.com/users/{0}/gists' -f $GitHubUserID
            $AllGist = Invoke-RestMethod -Uri $url -Method Get -Headers $headers -ErrorAction Stop
            $PRGist = $AllGist | Select-Object | Where-Object { $_.description -like 'PWSHModule-ConfigFile' }
        } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}
    }
    [System.Collections.generic.List[PSObject]]$CombinedModules = @()
    foreach ($List in $ListName) {
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Checking Config File"
            if ($LocalList) {
                $ListPath = Join-Path $Path -ChildPath "$($list).json"
                if (Test-Path $ListPath) { 
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Collecting Content"
                    $Content = Get-Content $ListPath | ConvertFrom-Json
                } else {Write-Warning "List file $($List) does not exist"}
            } else {
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Collecting Content"
                $Content = (Invoke-WebRequest -Uri ($PRGist.files.$($List)).raw_url -Headers $headers).content | ConvertFrom-Json -ErrorAction Stop
            }
            if ([string]::IsNullOrEmpty($Content.CreateDate) -or [string]::IsNullOrEmpty($Content.Modules)) {Write-Error 'Invalid Config File'}
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Adding to list."
            $Content.Modules | Where-Object {$_ -notlike $null -and $_.name -notin $CombinedModules.name} | ForEach-Object {$CombinedModules.Add($_)}
        } catch {Write-Warning "Error: `n`tMessage:$($_.Exception)"}
    }

    $InstallModuleSettings = @{
        AllowClobber       = $true
        Force              = $true
        SkipPublisherCheck = $true
    }
    if ($AllowPrerelease) {$InstallModuleSettings.add('AllowPrerelease', $true)}

    foreach ($module in ($CombinedModules | Sort-Object -Property name -Unique)) {
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Checking for installed module"
        if ($module.Version -like 'Latest') {
            $mod = Get-Module -Name $module.Name
            if (-not($mod)) {$mod = Get-Module -Name $module.name -ListAvailable}
            if (-not($mod)) { 
                try {
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Installing module"
                    Write-Host '[Installing] ' -NoNewline -ForegroundColor Yellow ; Write-Host 'Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($module.Name)" -ForegroundColor Green -NoNewline ; Write-Host ' to scope: ' -ForegroundColor DarkRed -NoNewline ; Write-Host "$($scope)" -ForegroundColor Cyan
                    Install-Module -Name $module.Name -Repository $module.Repository -Scope $Scope @InstallModuleSettings
                } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
            } else {
                try {
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Checking versions"
                    Write-Host '[Installed] ' -NoNewline -ForegroundColor Green ; Write-Host 'Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($module.Name) " -ForegroundColor Green -NoNewline ; Write-Host "$($mod.Path)" -ForegroundColor DarkRed
                    $OnlineMod = Find-Module -Name $module.name -Repository $module.Repository
                    [version]$Onlineversion = $OnlineMod.version 
                    [version]$Localversion = ($mod | Sort-Object -Property Version -Descending)[0].Version
                } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
                if ($Localversion -lt $Onlineversion) {
                    Write-Host "`t[Upgrading] " -NoNewline -ForegroundColor Yellow ; Write-Host 'Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($module.Name)" -ForegroundColor Green -NoNewline; Write-Host " v$($OnlineMod.version)" -ForegroundColor DarkRed
                    try {
                        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Updating module"
                        Update-Module -Name $module.Name -Force -ErrorAction Stop
                    } catch {
                        try {
                            Install-Module -Name $module.name -Scope $Scope -Repository $module.Repository @InstallModuleSettings
                        } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
                    }
                    Get-Module $module.name -ListAvailable | Remove-Module -Force -ErrorAction SilentlyContinue
                    $mods = (Get-Module $module.name -ListAvailable | Sort-Object -Property version -Descending) | Select-Object -Skip 1
                    foreach ($mod in $mods) {
                        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] UnInstalling module"
                        Write-Host "`t[Uninstalling] " -NoNewline -ForegroundColor Yellow ; Write-Host 'Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($module.Name)($($mod.Version)) " -ForegroundColor Green -NoNewline ; Write-Host "$($mod.Path)" -ForegroundColor DarkRed
                        try {
                            Uninstall-Module -Name $mod.name -RequiredVersion $mod.Version -Force
                        } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
                    }
                }
            }
        } else {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Checking installed module"
            $mod = Get-Module -Name $module.Name
            if (-not($mod)) {$mod = Get-Module -Name $module.name -ListAvailable}
            if ((-not($mod)) -or $mod.Version -lt $module.Version) {
                try {
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Installing module"
                    Write-Host '[Installing] ' -NoNewline -ForegroundColor Yellow ; Write-Host 'Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($module.Name)($($module.Version))" -ForegroundColor Green -NoNewline ; Write-Host ' to scope: ' -ForegroundColor DarkRed -NoNewline ; Write-Host "$($scope)" -ForegroundColor Cyan
                    Install-Module -Name $module.Name -Repository $module.Repository -RequiredVersion $module.Version -Scope $Scope @InstallModuleSettings
                } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
            } else {
                Write-Host '[Installed] ' -NoNewline -ForegroundColor Green ; Write-Host 'Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($module.Name) " -ForegroundColor Green -NoNewline ; Write-Host "$($mod.Path)" -ForegroundColor DarkRed
            }
        }
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) DONE]"
    }

} #end Function


$scriptblock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if ([bool]($PSDefaultParameterValues.Keys -like '*:GitHubUserID')) {(Get-PWSHModuleList).name | Where-Object {$_ -like "*$wordToComplete*"}}
}
Register-ArgumentCompleter -CommandName Install-PWSHModule -ParameterName ListName -ScriptBlock $scriptBlock
 
Export-ModuleMember -Function Install-PWSHModule
#endregion
 
#region Move-PWSHModuleBetweenScope.ps1
######## Function 5 of 12 ##################
# Function: Move-PWSHModuleBetweenScope
# Module: PWSHModule
# ModuleVersion: 0.1.21
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/08/20 12:38:44
# ModifiedOn: 2022/09/08 01:18:38
# Synopsis: Moves modules between scopes (CurrentUser and AllUsers).
#############################################
 
<#
.SYNOPSIS
Moves modules between scopes (CurrentUser and AllUsers).
 
.DESCRIPTION
Moves modules between scopes (CurrentUser and AllUsers).
 
.PARAMETER SourceScope
From where the modules will be copied.
 
.PARAMETER DestinationScope
To there the modules will be copied.
 
.PARAMETER ModuleName
Name of the modules to move. You can select multiple names or you can use * to select all.
 
.PARAMETER Repository
The repository will be used to install the module at the destination.
 
.EXAMPLE
Move-PWSHModuleBetweenScope -SourceScope D:\Documents\PowerShell\Modules -DestinationScope C:\Program Files\PowerShell\Modules -ModuleName PWSHMOdule -Repository psgallery
 
#>

Function Move-PWSHModuleBetweenScope {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PWSHModule/Move-PWSHModuleBetweenScope')]
    [OutputType([System.Object[]])]
    PARAM(
        [Parameter(Mandatory = $true)]
        [ValidateScript( { $IsAdmin = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
                if ($IsAdmin.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { $True }
                else { Throw 'Must be running an elevated prompt.' } })]
        [System.IO.DirectoryInfo]$SourceScope,

        [Parameter(Mandatory = $true)]
        [System.IO.DirectoryInfo]$DestinationScope,

        [Parameter(ValueFromPipeline, Mandatory)]
        [Alias('Name')]
        [string[]]$ModuleName,

        [string]$Repository = 'PSGallery'
    )

    if ($ModuleName -like 'All') {$ModuleName = (Get-ChildItem -Path $($SourceScope) -Directory).Name }

    foreach ($mod in $ModuleName) {
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Checking for installed module $($mod)"
        try {
            $MoveMod = Get-Module -Name $mod -ListAvailable -ErrorAction Stop | Where-Object {$_.path -like "$($SourceScope)*"} | Sort-Object -Property Version -Descending | Select-Object -First 1
        } catch {Write-Warning "Did not find $($ModuleName) in $($SourceScope)"}
        Write-Host '[Moving] ' -NoNewline -ForegroundColor Yellow ; Write-Host 'Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($MoveMod.Name)($($MoveMod.Version)) " -ForegroundColor Green -NoNewline ; Write-Host "$($DestinationScope)" -ForegroundColor DarkRed            
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) ADDING] to archive"
                (Get-Item $MoveMod.Path).directory.Parent.FullName | Compress-Archive -DestinationPath (Join-Path -Path $SourceScope -ChildPath 'PWSHModule_Move.zip') -Update -ErrorAction Stop
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) Deleteing folder"
                (Get-Item $MoveMod.Path).directory.Parent.FullName | Remove-Item -Recurse -Force
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) Saving] Module $($MoveMod.name)"
            Save-Module -Name $MoveMod.Name -RequiredVersion $MoveMod.Version -Repository $Repository -Force -AllowPrerelease -AcceptLicense -Path (Get-Item $DestinationScope).FullName -ErrorAction Stop
        } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) Complete"
    }
} #end Function
$scriptblock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $env:PSModulePath.Split(';') | ForEach-Object {"""$($_)"""}
}
Register-ArgumentCompleter -CommandName Move-PWSHModuleBetweenScope -ParameterName SourceScope -ScriptBlock $scriptBlock
Register-ArgumentCompleter -CommandName Move-PWSHModuleBetweenScope -ParameterName DestinationScope -ScriptBlock $scriptBlock

$scriptblock2 = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $ModList = @()
    $ModList += 'All'
    $ModList += ($fakeBoundParameters.SourceScope | Get-ChildItem -Directory).Name
    $ModList | Where-Object {$_ -like "*$wordToComplete*"}
}
Register-ArgumentCompleter -CommandName Move-PWSHModuleBetweenScope -ParameterName ModuleName -ScriptBlock $scriptBlock2

$scriptblock3 = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    (Get-Repository).name | Where-Object {$_ -like "*$wordToComplete*"}
}
Register-ArgumentCompleter -CommandName Move-PWSHModuleBetweenScope -ParameterName Repository -ScriptBlock $scriptBlock3
 
Export-ModuleMember -Function Move-PWSHModuleBetweenScope
#endregion
 
#region New-PWSHModuleList.ps1
######## Function 6 of 12 ##################
# Function: New-PWSHModuleList
# Module: PWSHModule
# ModuleVersion: 0.1.21
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/07/09 15:22:20
# ModifiedOn: 2022/08/25 23:29:18
# Synopsis: Add a new list to GitHub Gist.
#############################################
 
<#
.SYNOPSIS
Add a new list to GitHub Gist.
 
.DESCRIPTION
Add a new list to GitHub Gist.
 
.PARAMETER ListName
The File Name on GitHub Gist.
 
.PARAMETER Description
Summary of the function for the list.
 
.PARAMETER GitHubUserID
The GitHub User ID.
 
.PARAMETER GitHubToken
GitHub Token with access to the Users' Gist.
 
.EXAMPLE
New-PWSHModuleList -ListName Base -Description "These modules needs to be installed on all servers" -GitHubUserID smitpi -GitHubToken $GitHubToken
 
#>

Function New-PWSHModuleList {
    [Cmdletbinding( HelpURI = 'https://smitpi.github.io/PWSHModule/New-PWSHModuleList')]
    PARAM(
        [Parameter(Mandatory)]
        [string]$ListName,
        [Parameter(Mandatory)]
        [string]$Description,
        [Parameter(Mandatory)]
        [string]$GitHubUserID, 
        [Parameter(Mandatory)]
        [string]$GitHubToken
    )

    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Creating config"
    $NewConfig = [PSCustomObject]@{
        CreateDate   = (Get-Date -Format u)
        Description  = $Description
        Author       = "$($env:USERNAME.ToLower())"
        ModifiedDate = 'Unknown'
        ModifiedUser = 'Unknown'
        Modules      = [PSCustomObject]@{
            Name        = 'PWSHModule'
            Version     = 'Latest'
            Description = 'Uses a GitHub Gist File to install and maintain a list of PowerShell Modules'
            Repository  = 'PSGallery'
            Projecturi  = 'https://github.com/smitpi/PWSHModule'
        }
 } | ConvertTo-Json

    $ConfigFile = Join-Path $env:TEMP -ChildPath "$($ListName).json"
    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Create temp file"
    if (Test-Path $ConfigFile) {
        Write-Warning "Config File exists, Renaming file to $($ListName)-$(Get-Date -Format yyyyMMdd_HHmm).json"    
        try {
            Rename-Item $ConfigFile -NewName "$($ListName)-$(Get-Date -Format yyyyMMdd_HHmm).json" -Force -ErrorAction Stop | Out-Null
        } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message);exit"}
    }
    try {
        $NewConfig | Set-Content -Path $ConfigFile -Encoding utf8 -ErrorAction Stop
    } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}


    try {
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Connecting to Gist"
        $headers = @{}
        $auth = '{0}:{1}' -f $GitHubUserID, $GitHubToken
        $bytes = [System.Text.Encoding]::ASCII.GetBytes($auth)
        $base64 = [System.Convert]::ToBase64String($bytes)
        $headers.Authorization = 'Basic {0}' -f $base64

        $url = 'https://api.github.com/users/{0}/gists' -f $GitHubUserID
        $AllGist = Invoke-RestMethod -Uri $url -Method Get -Headers $headers -ErrorAction Stop
        $PRGist = $AllGist | Select-Object | Where-Object { $_.description -like 'PWSHModule-ConfigFile' }
    } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}

        
    if ([string]::IsNullOrEmpty($PRGist)) {
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Uploading to gist"
            $Body = @{}
            $files = @{}
            $Files["$($ListName)"] = @{content = ( Get-Content (Get-Item $ConfigFile).FullName -Encoding UTF8 | Out-String ) }
            $Body.files = $Files
            $Body.description = 'PWSHModule-ConfigFile'
            $json = ConvertTo-Json -InputObject $Body
            $json = [System.Text.Encoding]::UTF8.GetBytes($json)
            $null = Invoke-WebRequest -Headers $headers -Uri https://api.github.com/gists -Method Post -Body $json -ErrorAction Stop
            Write-Host '[Uploaded]' -NoNewline -ForegroundColor Yellow; Write-Host " $($ListName).json" -NoNewline -ForegroundColor Cyan; Write-Host ' to Github Gist' -ForegroundColor Green

        } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}
    } else {
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Uploading to Gist"
            $Body = @{}
            $files = @{}
            $Files["$($ListName)"] = @{content = ( Get-Content (Get-Item $ConfigFile).FullName -Encoding UTF8 | Out-String ) }
            $Body.files = $Files
            $Uri = 'https://api.github.com/gists/{0}' -f $PRGist.id
            $json = ConvertTo-Json -InputObject $Body
            $json = [System.Text.Encoding]::UTF8.GetBytes($json)
            $null = Invoke-WebRequest -Headers $headers -Uri $Uri -Method Patch -Body $json -ErrorAction Stop
            Write-Host '[Uploaded]' -NoNewline -ForegroundColor Yellow; Write-Host " $($ListName).json" -NoNewline -ForegroundColor Cyan; Write-Host ' to Github Gist' -ForegroundColor Green
        } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}
    }
    Write-Verbose "[$(Get-Date -Format HH:mm:ss) DONE]"


} #end Function
 
Export-ModuleMember -Function New-PWSHModuleList
#endregion
 
#region Remove-PWSHModule.ps1
######## Function 7 of 12 ##################
# Function: Remove-PWSHModule
# Module: PWSHModule
# ModuleVersion: 0.1.21
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/07/13 11:14:06
# ModifiedOn: 2022/09/07 17:32:17
# Synopsis: Remove module from the specified list.
#############################################
 
<#
.SYNOPSIS
Remove module from the specified list.
 
.DESCRIPTION
Remove module from the specified list.
 
.PARAMETER GitHubUserID
The GitHub User ID.
 
.PARAMETER GitHubToken
GitHub Token with access to the Users' Gist.
 
.PARAMETER ListName
The File Name on GitHub Gist.
 
.PARAMETER ModuleName
Module to remove.
 
.PARAMETER UninstallModule
Will uninstall the modules as well.
 
.EXAMPLE
Remove-PWSHModule -ListName base -ModuleName pslauncher -GitHubUserID smitpi -GitHubToken $GitHubToken
#>

Function Remove-PWSHModule {
    [Cmdletbinding(SupportsShouldProcess = $true, HelpURI = 'https://smitpi.github.io/PWSHModule/Remove-PWSHModule')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')]
    PARAM(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string[]]$ListName,
        [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
        [Alias('Name')]
        [string[]]$ModuleName,
        [Parameter(Mandatory)]
        [string]$GitHubUserID,
        [Parameter(Mandatory)]
        [string]$GitHubToken,
        [ValidateScript( { $IsAdmin = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
                if ($IsAdmin.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { $True }
                else { Throw 'Must be running an elevated prompt.' } })]
        [switch]$UninstallModule
    )
    begin {
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Connecting to Gist"
            $headers = @{}
            $auth = '{0}:{1}' -f $GitHubUserID, $GitHubToken
            $bytes = [System.Text.Encoding]::ASCII.GetBytes($auth)
            $base64 = [System.Convert]::ToBase64String($bytes)
            $headers.Authorization = 'Basic {0}' -f $base64

            $url = 'https://api.github.com/users/{0}/gists' -f $GitHubUserID
            $AllGist = Invoke-RestMethod -Uri $url -Method Get -Headers $headers -ErrorAction Stop
            $PRGist = $AllGist | Select-Object | Where-Object { $_.description -like 'PWSHModule-ConfigFile' }
        } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}
    }
    process {
        if ($pscmdlet.ShouldProcess('Target', 'Operation')) {
            foreach ($List in $ListName) {
                try {
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Checking config file."
                    $Content = (Invoke-WebRequest -Uri ($PRGist.files.$($List)).raw_url -Headers $headers).content | ConvertFrom-Json -ErrorAction Stop
                } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
                if ([string]::IsNullOrEmpty($Content.CreateDate) -or [string]::IsNullOrEmpty($Content.Modules)) {Write-Error 'Invalid Config File'}

                [System.Collections.ArrayList]$ModuleObject = @()
                $Content.Modules | ForEach-Object {[void]$ModuleObject.Add($_)
                }
                foreach ($mod in $ModuleName) {
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Find module to remove"
                    $Modremove = ($Content.Modules | Where-Object {$_.Name -like "*$Mod*"})
                    if ([string]::IsNullOrEmpty($Modremove) -or ($Modremove.name.count -gt 1)) {
                        Write-Error 'Module not found. Redefine your search'
                    } else {
                        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Removing module"
                        $ModuleObject.Remove($Modremove)
                        Write-Host '[Removed]' -NoNewline -ForegroundColor Yellow; Write-Host " $($Modremove.Name)" -NoNewline -ForegroundColor Cyan; Write-Host " from $($ListName)" -ForegroundColor Green
                        if ($UninstallModule) {
                            try {
                                Write-Host '[Uninstalling]' -NoNewline -ForegroundColor Yellow ; Write-Host 'All Versions of Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($mod) " -ForegroundColor Green
                                Uninstall-Module -Name $mod -AllVersions -Force -ErrorAction Stop
                            } catch {
                                Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"
                                Get-Module -Name $Mod -ListAvailable | ForEach-Object {
                                    try {
                                        Write-Host '[Deleting] ' -NoNewline -ForegroundColor Yellow ; Write-Host 'Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($_.Name)($($_.Version)) " -ForegroundColor Green -NoNewline ; Write-Host "$($_.Path)" -ForegroundColor DarkRed
                                        $Directory = Join-Path -Path (Get-Item $_.Path).FullName -ChildPath '..\..\' -Resolve
                                        Remove-Item -Path $Directory -Recurse -Force -ErrorAction Stop
                                    } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
                                }
                            }
                        }
                    }
                }
                try {
                    $Content.Modules = $ModuleObject | Sort-Object -Property name
                    $Content.ModifiedDate = "$(Get-Date -Format u)"
                    $content.ModifiedUser = "$($env:USERNAME.ToLower())"
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Uploading to gist"
                    $Body = @{}
                    $files = @{}
                    $Files["$($PRGist.files.$($List).Filename)"] = @{content = ( $Content | ConvertTo-Json | Out-String ) }
                    $Body.files = $Files
                    $Uri = 'https://api.github.com/gists/{0}' -f $PRGist.id
                    $json = ConvertTo-Json -InputObject $Body
                    $json = [System.Text.Encoding]::UTF8.GetBytes($json)
                    $null = Invoke-WebRequest -Headers $headers -Uri $Uri -Method Patch -Body $json -ErrorAction Stop
                    Write-Host '[Uploaded] ' -NoNewline -ForegroundColor Yellow; Write-Host " List: $($List)" -NoNewline -ForegroundColor Cyan; Write-Host ' to Github Gist' -ForegroundColor Green
                } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}
            }
        }
    }
    end {}
} #end Function
$scriptblock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if ([bool]($PSDefaultParameterValues.Keys -like "*:GitHubUserID")) {(Get-PWSHModuleList).name | Where-Object {$_ -like "*$wordToComplete*"}}
}

$scriptblock2 = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if (($PSDefaultParameterValues.Keys -like "*:GitHubUserID")) {
    (Show-PWSHModule -ListName $fakeBoundParameters.Listname -ErrorAction SilentlyContinue).name | Where-Object {$_ -like "*$wordToComplete*"}
    }
}
Register-ArgumentCompleter -CommandName Remove-PWSHModule -ParameterName ListName -ScriptBlock $scriptBlock
Register-ArgumentCompleter -CommandName Remove-PWSHModule -ParameterName ModuleName -ScriptBlock $scriptBlock2
 
Export-ModuleMember -Function Remove-PWSHModule
#endregion
 
#region Remove-PWSHModuleList.ps1
######## Function 8 of 12 ##################
# Function: Remove-PWSHModuleList
# Module: PWSHModule
# ModuleVersion: 0.1.21
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/07/31 11:14:51
# ModifiedOn: 2022/09/07 17:32:17
# Synopsis: Deletes a list from GitHub Gist
#############################################
 
<#
.SYNOPSIS
Deletes a list from GitHub Gist
 
.DESCRIPTION
Deletes a list from GitHub Gist
 
.PARAMETER ListName
The Name of the list to remove.
 
.PARAMETER GitHubUserID
The GitHub User ID.
 
.PARAMETER GitHubToken
GitHub Token with access to the Users' Gist.
 
.EXAMPLE
Remove-PWSHModuleList -ListName Base -GitHubUserID smitpi -GitHubToken $GitHubToken
 
#>

Function Remove-PWSHModuleList {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PWSHModule/Remove-PWSHModuleList')]
    [OutputType([System.Object[]])]
    PARAM(
        [Parameter(Mandatory = $true)]
        [string[]]$ListName,
        [Parameter(Mandatory = $true)]
        [string]$GitHubUserID, 
        [Parameter(Mandatory = $true)]
        [string]$GitHubToken
    )

    try {
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Connect to gist"
        $headers = @{}
        $auth = '{0}:{1}' -f $GitHubUserID, $GitHubToken
        $bytes = [System.Text.Encoding]::ASCII.GetBytes($auth)
        $base64 = [System.Convert]::ToBase64String($bytes)
        $headers.Authorization = 'Basic {0}' -f $base64

        $url = 'https://api.github.com/users/{0}/gists' -f $GitHubUserID
        $AllGist = Invoke-RestMethod -Uri $url -Method Get -Headers $headers -ErrorAction Stop
        $PRGist = $AllGist | Select-Object | Where-Object { $_.description -like 'PWSHModule-ConfigFile' }
    } catch {throw "Can't connect to gist:`n $($_.Exception.Message)"}

    foreach ($List in $ListName) {
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Create object"
        $CheckExist = $PRGist.files | Get-Member -MemberType NoteProperty | Where-Object {$_.name -like $List}
        if (-not([string]::IsNullOrEmpty($CheckExist))) {
            try {
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Remove list from Gist"
                $Body = @{}
                $files = @{}
                $Files["$($List)"] = $null
                $Body.files = $Files
                $Uri = 'https://api.github.com/gists/{0}' -f $PRGist.id
                $json = ConvertTo-Json -InputObject $Body
                $json = [System.Text.Encoding]::UTF8.GetBytes($json)
                $null = Invoke-WebRequest -Headers $headers -Uri $Uri -Method Patch -Body $json -ErrorAction Stop
                Write-Host '[Removed]' -NoNewline -ForegroundColor Yellow; Write-Host " $($List)" -NoNewline -ForegroundColor Cyan; Write-Host ' from Github Gist' -ForegroundColor DarkRed
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] updated gist."
            } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}
        }
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) DONE]"
    }
} #end Function

$scriptblock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if ([bool]($PSDefaultParameterValues.Keys -like '*:GitHubUserID')) {(Get-PWSHModuleList).name | Where-Object {$_ -like "*$wordToComplete*"}}
}
Register-ArgumentCompleter -CommandName Remove-PWSHModuleList -ParameterName ListName -ScriptBlock $scriptBlock
 
Export-ModuleMember -Function Remove-PWSHModuleList
#endregion
 
#region Save-PWSHModule.ps1
######## Function 9 of 12 ##################
# Function: Save-PWSHModule
# Module: PWSHModule
# ModuleVersion: 0.1.21
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/07/13 10:26:41
# ModifiedOn: 2022/09/07 17:32:17
# Synopsis: Saves the modules from the specified list to a folder.
#############################################
 
<#
.SYNOPSIS
Saves the modules from the specified list to a folder.
 
.DESCRIPTION
Saves the modules from the specified list to a folder.
 
.PARAMETER ListName
The File Name on GitHub Gist.
 
.PARAMETER AsNuGet
Save in the NuGet format
 
.PARAMETER Path
Where to save
 
.PARAMETER GitHubUserID
The GitHub User ID.
 
.PARAMETER PublicGist
Select if the list is hosted publicly.
 
.PARAMETER GitHubToken
GitHub Token with access to the Users' Gist.
 
.EXAMPLE
Save-PWSHModule -ListName extended -AsNuGet -Path c:\temp\ -GitHubUserID smitpi -GitHubToken $GitHubToken
 
#>

Function Save-PWSHModule {
    [Cmdletbinding(DefaultParameterSetName = 'Private', HelpURI = 'https://smitpi.github.io/PWSHModule/Save-PWSHModule')]
    PARAM(
        [Parameter(Mandatory = $true)]
        [string[]]$ListName,
        [switch]$AsNuGet,
        [ValidateScript( { if (Test-Path $_) { $true }
                else { New-Item -Path $_ -ItemType Directory -Force | Out-Null; $true }
            })]
        [System.IO.DirectoryInfo]$Path = 'C:\Temp',
        [Parameter(Mandatory = $true)]
        [string]$GitHubUserID, 
        [Parameter(ParameterSetName = 'Public')]
        [switch]$PublicGist,
        [Parameter(ParameterSetName = 'Private')]
        [string]$GitHubToken
    )

    try {
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Connect to Gist"
        $headers = @{}
        $auth = '{0}:{1}' -f $GitHubUserID, $GitHubToken
        $bytes = [System.Text.Encoding]::ASCII.GetBytes($auth)
        $base64 = [System.Convert]::ToBase64String($bytes)
        $headers.Authorization = 'Basic {0}' -f $base64

        $url = 'https://api.github.com/users/{0}/gists' -f $GitHubUserID
        $AllGist = Invoke-RestMethod -Uri $url -Method Get -Headers $headers -ErrorAction Stop
        $PRGist = $AllGist | Select-Object | Where-Object { $_.description -like 'PWSHModule-ConfigFile' }
    } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}

    foreach ($list in $ListName) {
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Checking Config File"
            $Content = (Invoke-WebRequest -Uri ($PRGist.files.$($List)).raw_url -Headers $headers).content | ConvertFrom-Json -ErrorAction Stop
        } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
        if ([string]::IsNullOrEmpty($Content.CreateDate) -or [string]::IsNullOrEmpty($Content.Modules)) {Write-Error 'Invalid Config File'}

        foreach ($module in $Content.Modules) {
            if ($module.Version -like 'Latest') {
                if ($AsNuGet) {
                    try {
                        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Downloading"
                        Write-Host '[Downloading] ' -NoNewline -ForegroundColor Yellow ; Write-Host 'NuGet: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($module.Name) " -ForegroundColor Green -NoNewline ; Write-Host "Path: $($Path)" -ForegroundColor DarkRed
                        Save-Package -Name $module.Name -Provider NuGet -Source (Get-PSRepository -Name $module.Repository).SourceLocation -Path $Path | Out-Null
                    } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
                } else {
                    try {
                        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Downloading"
                        Write-Host '[Downloading] ' -NoNewline -ForegroundColor Yellow ; Write-Host 'Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($module.Name) " -ForegroundColor Green -NoNewline ; Write-Host "Path: $($Path)" -ForegroundColor DarkRed
                        Save-Module -Name $module.name -Repository $module.Repository -Path $Path
                    } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
                }
            } else {
                if ($AsNuGet) {
                    try {
                        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Downloading"
                        Write-Host '[Downloading] ' -NoNewline -ForegroundColor Yellow ; Write-Host 'NuGet: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($module.Name)(ver $($module.version)) " -ForegroundColor Green -NoNewline ; Write-Host "Path: $($Path)" -ForegroundColor DarkRed
                        Save-Package -Name $module.Name -Provider NuGet -Source (Get-PSRepository -Name $module.Repository).SourceLocation -RequiredVersion $module.Version -Path $Path | Out-Null
                    } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
                } else {
                    try {
                        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Downloading"
                        Write-Host '[Downloading] ' -NoNewline -ForegroundColor Yellow ; Write-Host 'Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($module.Name)(ver $($module.version)) " -ForegroundColor Green -NoNewline ; Write-Host "Path: $($Path)" -ForegroundColor DarkRed
                        Save-Module -Name $module.name -Repository $module.Repository -RequiredVersion $module.Version -Path $Path
                    } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
                }

            }
        }
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) DONE]"
    }
} #end Function


$scriptblock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if ([bool]($PSDefaultParameterValues.Keys -like "*:GitHubUserID")) {(Get-PWSHModuleList).name | Where-Object {$_ -like "*$wordToComplete*"}}
}
Register-ArgumentCompleter -CommandName Save-PWSHModule -ParameterName ListName -ScriptBlock $scriptBlock
 
Export-ModuleMember -Function Save-PWSHModule
#endregion
 
#region Save-PWSHModuleList.ps1
######## Function 10 of 12 ##################
# Function: Save-PWSHModuleList
# Module: PWSHModule
# ModuleVersion: 0.1.21
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/09/07 16:36:26
# ModifiedOn: 2022/09/07 17:58:18
# Synopsis: Save the Gist file to a local file
#############################################
 
<#
.SYNOPSIS
Save the Gist file to a local file
 
.DESCRIPTION
Save the Gist file to a local file
 
.PARAMETER ListName
Name of the list.
 
.PARAMETER GitHubUserID
User with access to the gist.
 
.PARAMETER PublicGist
Select if the list is hosted publicly.
 
.PARAMETER GitHubToken
The token for that gist.
 
.PARAMETER Path
Directory where files will be saved.
 
.EXAMPLE
Save-PWSHModuleList -ListName Base,twee -Path C:\temp
 
#>

Function Save-PWSHModuleList {
    [Cmdletbinding(DefaultParameterSetName = 'Set1', HelpURI = 'https://smitpi.github.io/PWSHModule/Save-PWSHModuleList')]
    PARAM(
        [Parameter(Mandatory)]
        [string[]]$ListName,
        [Parameter(Mandatory)]
        [System.IO.DirectoryInfo]$Path,
        [Parameter(Mandatory)]
        [string]$GitHubUserID, 
        [Parameter(ParameterSetName = 'Public')]
        [switch]$PublicGist,
        [Parameter(ParameterSetName = 'Private')]
        [string]$GitHubToken
    )

    try {
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Connect to gist"
        $headers = @{}
        $auth = '{0}:{1}' -f $GitHubUserID, $GitHubToken
        $bytes = [System.Text.Encoding]::ASCII.GetBytes($auth)
        $base64 = [System.Convert]::ToBase64String($bytes)
        $headers.Authorization = 'Basic {0}' -f $base64

        $url = 'https://api.github.com/users/{0}/gists' -f $GitHubUserID
        $AllGist = Invoke-RestMethod -Uri $url -Method Get -Headers $headers -ErrorAction Stop
        $PRGist = $AllGist | Select-Object | Where-Object { $_.description -like 'PWSHModule-ConfigFile' }
    } catch {throw "Can't connect to gist:`n $($_.Exception.Message)"}

    foreach ($List in $ListName) {
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Checking Config File"
            $Content = (Invoke-WebRequest -Uri ($PRGist.files.$($List)).raw_url -Headers $headers).content | ConvertFrom-Json -ErrorAction Stop
        } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
        if ([string]::IsNullOrEmpty($Content.CreateDate) -or [string]::IsNullOrEmpty($Content.Modules)) {Write-Error 'Invalid Config File'}
        $Content | ConvertTo-Json -Depth 5 | Set-Content -Path (Join-Path $Path -ChildPath "$($list).json") -Force
        Write-Host '[Saved]' -NoNewline -ForegroundColor Yellow; Write-Host " $($List) " -NoNewline -ForegroundColor Cyan; Write-Host "to $((Join-Path $Path -ChildPath "$($list).json"))" -ForegroundColor Green
    }
    Write-Verbose "[$(Get-Date -Format HH:mm:ss) DONE]"
} #end Function

$scriptblock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if ([bool]($PSDefaultParameterValues.Keys -like '*:GitHubUserID')) {(Get-PWSHModuleList).name | Where-Object {$_ -like "*$wordToComplete*"}}
}
Register-ArgumentCompleter -CommandName Save-PWSHModuleList -ParameterName ListName -ScriptBlock $scriptBlock
 
Export-ModuleMember -Function Save-PWSHModuleList
#endregion
 
#region Show-PWSHModule.ps1
######## Function 11 of 12 ##################
# Function: Show-PWSHModule
# Module: PWSHModule
# ModuleVersion: 0.1.21
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/07/09 15:57:20
# ModifiedOn: 2022/09/02 12:43:12
# Synopsis: Show the details of the modules in a list.
#############################################
 
<#
.SYNOPSIS
Show the details of the modules in a list.
 
.DESCRIPTION
Show the details of the modules in a list.
 
.PARAMETER ListName
The File Name on GitHub Gist.
 
.PARAMETER CompareInstalled
Compare the list to what is installed.
 
.PARAMETER ShowProjectURI
Will open the browser to the the project URL.
 
.PARAMETER GitHubUserID
The GitHub User ID.
 
.PARAMETER PublicGist
Select if the list is hosted publicly.
 
.PARAMETER GitHubToken
GitHub Token with access to the Users' Gist.
 
.EXAMPLE
Show-PWSHModule -ListName Base -GitHubUserID smitpi -GitHubToken $GitHubToken
 
#>

Function Show-PWSHModule {
    [Cmdletbinding(DefaultParameterSetName = 'Private', HelpURI = 'https://smitpi.github.io/PWSHModule/Show-PWSHModule')]
    PARAM(        
        [Parameter(Mandatory = $true)]
        [string[]]$ListName,
        [switch]$CompareInstalled,
        [switch]$ShowProjectURI,
        [Parameter(Mandatory = $true)]
        [string]$GitHubUserID, 
        [Parameter(ParameterSetName = 'Public')]
        [switch]$PublicGist,
        [Parameter(ParameterSetName = 'Private')]
        [string]$GitHubToken
    )

    try {
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Connecting to Gist"
        $headers = @{}
        $auth = '{0}:{1}' -f $GitHubUserID, $GitHubToken
        $bytes = [System.Text.Encoding]::ASCII.GetBytes($auth)
        $base64 = [System.Convert]::ToBase64String($bytes)
        $headers.Authorization = 'Basic {0}' -f $base64

        $url = 'https://api.github.com/users/{0}/gists' -f $GitHubUserID
        $AllGist = Invoke-RestMethod -Uri $url -Method Get -Headers $headers -ErrorAction Stop
        $PRGist = $AllGist | Select-Object | Where-Object { $_.description -like 'PWSHModule-ConfigFile' }
    } catch {throw "Can't connect to gist:`n $($_.Exception.Message)"}

    [System.Collections.ArrayList]$ModuleObject = @()        
    $index = 0
    foreach ($List in $ListName) {
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Checking config file"
            $Content = (Invoke-WebRequest -Uri ($PRGist.files.$($List)).raw_url -Headers $headers).content | ConvertFrom-Json -ErrorAction Stop
        } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
        if ([string]::IsNullOrEmpty($Content.CreateDate) -or [string]::IsNullOrEmpty($Content.Modules)) {Throw 'Invalid Config File'}

        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Creating object"
        $Content.Modules | ForEach-Object {                
            [void]$ModuleObject.Add([PSCustomObject]@{
                    Index       = $index
                    Name        = $_.Name
                    List        = $List
                    Version     = $_.version
                    Description = $_.Description
                    Repository  = $_.Repository
                    Projecturi  = $_.projecturi
                })
            $index++
        }
    }
    if ($CompareInstalled) {
        [System.Collections.ArrayList]$CompareObject = @()        
        $index = 0
        foreach ($CompareModule in $ModuleObject) {
            try {
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Online: $($CompareModule.name)"
                if ($CompareModule.Version -like 'Latest') {
                    $online = Find-Module -Name $CompareModule.name -Repository $CompareModule.Repository 
                } else {
                    $online = Find-Module -Name $CompareModule.name -Repository $CompareModule.Repository -RequiredVersion $CompareModule.Version
                }
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Local: $($CompareModule.name)"
                $local = $null
                $local = Get-Module -Name $CompareModule.Name -ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1
                if ([string]::IsNullOrEmpty($local)) {
                    $InstallVer = 'NotInstalled'
                    $InstallCount = 'NotInstalled'
                    $InstallFolder = 'NotInstalled'
                } else {
                    $InstallVer = $local.Version
                    $InstallCount = (Get-Module -Name $CompareModule.Name -ListAvailable).count
                    $InstallFolder = (Get-Item $local.Path).DirectoryName
                }
                if ($local.Version -lt $online.Version) {$update = $true}
                else {$update = $false}
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Building List with module: $($CompareModule.name)"
                [void]$CompareObject.Add([PSCustomObject]@{
                        Index           = $index
                        Name            = $CompareModule.Name
                        List            = $CompareModule.List
                        Publisheddate   = [datetime]$online.PublishedDate
                        UpdateDate      = [datetime]$online.AdditionalMetadata.lastUpdated
                        InstalledVer    = $InstallVer
                        OnlineVer       = $online.Version
                        UpdateAvailable = $update
                        InstallCount    = $InstallCount
                        Folder          = $InstallFolder
                        Description     = $CompareModule.Description
                        Repository      = $CompareModule.Repository
                    })
            } catch {Write-Warning "Error $($CompareModule.Name): `n`tMessage:$($_.Exception.Message)"}
            $index++
        }
        $CompareObject
    } else {$ModuleObject}
    if ($ShowProjectURI) {
        Write-Output ' '
        [int]$IndexURI = Read-Host 'Module Index Number'
        if (-not([string]::IsNullOrEmpty($Content.Modules[$IndexURI].projecturi))) {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] open url"
            Start-Process "$($Content.Modules[$IndexURI].projecturi)"
        } else { Write-Warning 'NotInstalled ProjectURI'}
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) DONE]"
    }
} #end Function


$scriptblock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if ([bool]($PSDefaultParameterValues.Keys -like "*:GitHubUserID")) {(Show-PWSHModuleList).name | Where-Object {$_ -like "*$wordToComplete*"}}
}
Register-ArgumentCompleter -CommandName Show-PWSHModule -ParameterName ListName -ScriptBlock $scriptBlock
 
Export-ModuleMember -Function Show-PWSHModule
#endregion
 
#region Uninstall-PWSHModule.ps1
######## Function 12 of 12 ##################
# Function: Uninstall-PWSHModule
# Module: PWSHModule
# ModuleVersion: 0.1.21
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/07/20 19:06:13
# ModifiedOn: 2022/09/07 17:32:17
# Synopsis: Will uninstall the module from the system.
#############################################
 
<#
.SYNOPSIS
Will uninstall the module from the system.
 
.DESCRIPTION
Will uninstall the module from the system. Select OldVersions to remove duplicates only.
 
.PARAMETER ListName
The File Name on GitHub Gist.
 
.PARAMETER ModuleName
Name of the module to uninstall. Use * to select all modules in the list.
 
.PARAMETER UninstallOldVersions
Will uninstall old versions of All modules.
 
.PARAMETER ForceUninstall
Will force delete the base folder if uninstall fail.
 
.PARAMETER GitHubUserID
The GitHub User ID.
 
.PARAMETER PublicGist
Select if the list is hosted publicly.
 
.PARAMETER GitHubToken
GitHub Token with access to the Users' Gist.
 
.EXAMPLE
Uninstall-PWSHModule -ListName base -OldVersions -GitHubUserID smitpi -PublicGist
 
#>

Function Uninstall-PWSHModule {
    [Cmdletbinding(DefaultParameterSetName = 'Private', HelpURI = 'https://smitpi.github.io/PWSHModule/Install-PWSHModule')]
    PARAM(
        [Parameter(Position = 0, Mandatory, ParameterSetName = 'List')]
        [ValidateScript( { $IsAdmin = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
                if ($IsAdmin.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { $True }
                else { Throw 'Must be running an elevated prompt.' } })]
        [string[]]$ListName,

        [Parameter(Position = 1, Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'List')]
        [Alias('Name')]
        [string[]]$ModuleName,

        [Parameter(Position = 0, ParameterSetName = 'OldVersions')]
        [switch]$UninstallOldVersions,

        [Parameter(Position = 1, ParameterSetName = 'OldVersions')]
        [switch]$ForceUninstall,

        [Parameter(Mandatory, ParameterSetName = 'List')]
        [string]$GitHubUserID,

        [Parameter(ParameterSetName = 'Public')]
        [Parameter(ParameterSetName = 'List')]
        [switch]$PublicGist,

        [Parameter(ParameterSetName = 'Private')]
        [Parameter(ParameterSetName = 'List')]
        [string]$GitHubToken
    )

    begin {
        if (-not($UninstallOldVersions)) {
            try {
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Connect to Gist"
                $headers = @{}
                $auth = '{0}:{1}' -f $GitHubUserID, $GitHubToken
                $bytes = [System.Text.Encoding]::ASCII.GetBytes($auth)
                $base64 = [System.Convert]::ToBase64String($bytes)
                $headers.Authorization = 'Basic {0}' -f $base64

                $url = 'https://api.github.com/users/{0}/gists' -f $GitHubUserID
                $AllGist = Invoke-RestMethod -Uri $url -Method Get -Headers $headers -ErrorAction Stop
                $PRGist = $AllGist | Select-Object | Where-Object { $_.description -like 'PWSHModule-ConfigFile' }
            } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}
        }
    }
    process {
        foreach ($List in $ListName) {
            try {
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Checking Config File"
                $Content = (Invoke-WebRequest -Uri ($PRGist.files.$($List)).raw_url -Headers $headers).content | ConvertFrom-Json -ErrorAction Stop
            } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
            if ([string]::IsNullOrEmpty($Content.CreateDate) -or [string]::IsNullOrEmpty($Content.Modules)) {Write-Error 'Invalid Config File'}
            [System.Collections.ArrayList]$CollectObject = @()

            foreach ($collectmod in $ModuleName) {
                $Content.Modules | Where-Object {$_.name -like $collectmod} | ForEach-Object {[void]$CollectObject.Add($_)}
            }
            
        }
    }
    end {
        foreach ($module in ($CollectObject | Select-Object -Unique) ) {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Checking for installed module"
            try {
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Uninstalling module $($module.Name)"
                Write-Host '[Uninstalling]' -NoNewline -ForegroundColor Yellow ; Write-Host 'All Versions of Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($module.Name) " -ForegroundColor Green
                Uninstall-Module -Name $module.Name -AllVersions -Force -ErrorAction Stop
            } catch { Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
        }
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) DONE]"
    
        if ($UninstallOldVersions) {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Checking for duplicate module"
            $DuplicateMods = Get-Module -list | Where-Object path -NotMatch 'windows\\system32' | Group-Object -Property name | Where-Object count -GT 1
            foreach ($Duplicate in $DuplicateMods) {
                $Duplicate.Group | Sort-Object -Property version -Descending | Select-Object -Skip 1 | ForEach-Object {
                    Write-Host '[Uninstalling]' -NoNewline -ForegroundColor Yellow ; Write-Host ' Duplicate Module: ' -NoNewline -ForegroundColor Cyan ; Write-Host "$($Duplicate.Name) " -ForegroundColor Green -NoNewline ; Write-Host " [$($_.version)] - $($_.path) " -ForegroundColor DarkRed
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Uninstalling duplicate $($Duplicate.Name) $($_.Path)"
                    try {
                        Uninstall-Module -Name $Duplicate.Name -RequiredVersion $_.Version -Force -AllowPrerelease -ErrorAction Stop
                    } catch {
                        Write-Warning "Uninstall for module $($Duplicate.Name) Failed.`n`tMessage:$($_.Exception.Message)"
                        if ($ForceUninstall) {
                            try {
                                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Uninstalling duplicate $($Duplicate.Name) $($_.Path) - Force delete folder"
                                $Modpath = Get-Item (Join-Path $Duplicate.Group[1].Path -ChildPath '..\' -Resolve)
                                $Modpath | Remove-Item -Recurse -Force
                            } catch { Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
                        }
                    }
                }
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) DONE] $($Duplicate.Name)"
            }
        }
    }
} #end Function


$scriptblock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if ([bool]($PSDefaultParameterValues.Keys -like '*:GitHubUserID')) {(Get-PWSHModuleList).name | Where-Object {$_ -like "*$wordToComplete*"}}
}
Register-ArgumentCompleter -CommandName Uninstall-PWSHModule -ParameterName ListName -ScriptBlock $scriptBlock

$scriptblock2 = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if (($PSDefaultParameterValues.Keys -like '*:GitHubUserID')) {
    (Show-PWSHModule -ListName $fakeBoundParameters.Listname -ErrorAction SilentlyContinue).name | Where-Object {$_ -like "*$wordToComplete*"}
    }
}
Register-ArgumentCompleter -CommandName Uninstall-PWSHModule -ParameterName ModuleName -ScriptBlock $scriptBlock2

 
Export-ModuleMember -Function Uninstall-PWSHModule
#endregion
 
#endregion