PSPackageMan.psm1

#region Public Functions
#region Add-PSPackageManAppToList.ps1
######## Function 1 of 11 ##################
# Function: Add-PSPackageManAppToList
# Module: PSPackageMan
# ModuleVersion: 0.1.3
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/09/02 19:34:01
# ModifiedOn: 2022/09/03 08:42:33
# Synopsis: Add an app to one more of the predefined GitHub Gist Lists.
#############################################
 
<#
.SYNOPSIS
 Add an app to one more of the predefined GitHub Gist Lists.
 
.DESCRIPTION
 Add an app to one more of the predefined GitHub Gist Lists.
 
.PARAMETER ListName
Name of the list.
 
.PARAMETER SearchString
Application name to search for.
 
.PARAMETER PackageManager
Which app manager to use (Chocolatey or winget)
 
.PARAMETER ChocoSource
Chocolatey source
 
.PARAMETER WingetSource
Winget source.
 
.PARAMETER GitHubUserID
User with access to the gist.
 
.PARAMETER GitHubToken
The token for that gist.
 
.PARAMETER MoreOptions
Select for more search options.
 
.PARAMETER ChocoSource
Chocolatey source
 
.PARAMETER Exact
Limits the search to the exact search string.
 
.EXAMPLE
Add-PSPackageManAppToList -ListName twee -Name speedtest -PackageManager Winget -GitHubUserID $User -GitHubToken $GitHubToken
 
#>

Function Add-PSPackageManAppToList {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PSPackageMan/Add-PSPackageManAppToList')]
    [OutputType([System.Object[]])]
    PARAM(
        [Parameter(Mandatory)]
        [string[]]$ListName,
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)]
        [Alias('Id', 'PackageIdentifier', 'Name')]
        [string[]]$SearchString,
        [ValidateSet('Chocolatey', 'Winget')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]$PackageManager,
        [Parameter(Mandatory)]
        [string]$GitHubUserID,
        [Parameter(Mandatory)]
        [string]$GitHubToken,
        [string]$ChocoSource,
        [switch]$Exact
    )

    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 'PSPackageMan-ConfigFile' }
        } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}
        [System.Collections.Generic.List[PSCustomObject]]$NewAppObject = @()
    }
    process {
        foreach ($NewApp in $SearchString) {
            try {
                Write-Color '[Searching]', " $($NewApp)" -Color Yellow, Gray
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] NewApp $($newapp)"
                [System.Collections.Generic.List[PSCustomObject]]$SearchResult = @()
                $SearchParams = $PSBoundParameters
                [void]$SearchParams.Remove('ListName')
                [void]$SearchParams.Remove('GithubUserID')
                [void]$SearchParams.Remove('GitHubToken')
                [void]$SearchParams.Remove('SearchString')
                Search-PSPackageManApp -SearchString $NewApp @SearchParams | ForEach-Object {$SearchResult.Add($_)}
                if ($SearchResult.Count -eq 1) {
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] Adding to object"
                    $NewAppObject.Add([PSCustomObject]@{
                            Name           = $SearchResult.Name
                            Id             = $SearchResult.Id
                            PackageManager = $PackageManager
                            Source         = $SearchResult.source
                        })
                } elseif ($SearchResult.Count -gt 1) {
                    Write-Color 'Please pick from below for', " $($NewApp)" -Color Gray, Yellow -LinesBefore 2 -LinesAfter 1
                    $index = 0
                    $SearchResult | ForEach-Object {
                        Write-Color "$($index)) ", "$($_.name)", " [$($_.version)]" -Color Yellow, Green, Cyan
                        $index++ 
                    }
                    Write-Host ''
                    [int]$PickIndex = Read-Host 'Choose'
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] Adding to object"
                    $NewAppObject.Add([PSCustomObject]@{
                            Name           = $SearchResult[$PickIndex].Name
                            Id             = $SearchResult[$PickIndex].Id
                            PackageManager = $PackageManager
                            Source         = $SearchResult[$PickIndex].source
                        })
                } else {Write-Error 'No App Found'}
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] Done adding $($newapp)"
            } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
        }        
    }
    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)"}
            try {
                [System.Collections.Generic.List[PSCustomObject]]$AppObject = @()
                $Content.Apps | Where-Object {$_ -notlike $null} | ForEach-Object {
                    if ($AppObject.Exists({ -not (Compare-Object $args[0].psobject.properties.value $_.psobject.Properties.value) })) {
                        Write-Color 'Duplicate Found', " ListName: $($list)", " Name: $($_.name)" -Color Gray, DarkYellow, DarkCyan
                    } else {$AppObject.Add($_)}
                }
                $NewAppObject | Where-Object {$_ -notlike $null} | ForEach-Object {
                    if ($AppObject.Exists({ -not (Compare-Object $args[0].psobject.properties.value $_.psobject.Properties.value) })) {
                        Write-Color 'Duplicate Found', " ListName: $($list)", " Name: $($_.name)" -Color Gray, DarkYellow, DarkCyan
                    } else {$AppObject.Add($_)}
                }
            } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) END] Completing and sorting object"
            $Content.Apps = $AppObject
            $Content.ModifiedDate = "$(Get-Date -Format u)"
            $content.ModifiedUser = "$($env:USERNAME.ToLower())@$($env:USERDNSDOMAIN.ToLower())"
            try {
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) END] 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 = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if ([bool]($PSDefaultParameterValues.Keys -like '*:GitHubUserID')) {(Get-PSPackageManAppList).name }
}
Register-ArgumentCompleter -CommandName Add-PSPackageManAppToList -ParameterName ListName -ScriptBlock $scriptblock
Register-ArgumentCompleter -CommandName Add-PSPackageManAppToList -ParameterName ChocoSource -ScriptBlock {choco source --limit-output | ForEach-Object {$_.split('|')[0]}}
 
Export-ModuleMember -Function Add-PSPackageManAppToList
#endregion
 
#region Add-PSPackageManDefaultsToProfile.ps1
######## Function 2 of 11 ##################
# Function: Add-PSPackageManDefaultsToProfile
# Module: PSPackageMan
# ModuleVersion: 0.1.3
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/09/02 19:43:27
# ModifiedOn: 2022/09/03 16:54:43
# Synopsis: Add the parameter to PSDefaultParameters and also your profile.
#############################################
 
<#
.SYNOPSIS
Add the parameter to PSDefaultParameters and also your profile.
 
.DESCRIPTION
Add the parameter to PSDefaultParameters and also your profile.
 
.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 RemoveConfig
Remove the config from your profile.
 
.EXAMPLE
Add-PSPackageManDefaultsToProfile -GitHubUserID $user -PublicGist
 
#>

Function Add-PSPackageManDefaultsToProfile {
        [Cmdletbinding(HelpURI = "https://smitpi.github.io/PSPackageMan/Add-PSPackageManDefaultsToProfile")]
        [OutputType([System.Object[]])]
    PARAM(
        [Parameter(Mandatory = $true)]
        [string]$GitHubUserID, 
        [Parameter(ParameterSetName = 'Public')]
        [switch]$PublicGist,
        [Parameter(ParameterSetName = 'Private')]
        [string]$GitHubToken,
        [switch]$RemoveConfig
    )

    ## TODO Add remove config from profile.

    if ($PublicGist) {
        $PSDefaultParameterValues.Add('*PSPackageMan*:GitHubUserID',"$($GitHubUserID)")
        $PSDefaultParameterValues.Add('*PSPackageMan*:PublicGist',$true)

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

    } else {
        $PSDefaultParameterValues.Add('*PSPackageMan*:GitHubUserID',"$($GitHubUserID)")
        $PSDefaultParameterValues.Add('*PSPackageMan*:GitHubToken',"$($GitHubToken)")
        
        $ToAppend = @"
         
#region PSPackageMan Defaults
`$PSDefaultParameterValues['*PSPackageMan*:GitHubUserID'] = "$($GitHubUserID)"
`$PSDefaultParameterValues['*PSPackageMan*:GitHubToken'] = "$($GitHubToken)"
#endregion PSPackageMan
 
"@

    }

    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 '*PSPackageMan*'}
        $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-PSPackageManDefaultsToProfile
#endregion
 
#region Get-PSPackageManAppList.ps1
######## Function 3 of 11 ##################
# Function: Get-PSPackageManAppList
# Module: PSPackageMan
# ModuleVersion: 0.1.3
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/09/02 19:24:07
# ModifiedOn: 2022/09/03 08:36:07
# Synopsis: Show a List of all the GitHub Gist app Lists.
#############################################
 
<#
.SYNOPSIS
Show a List of all the GitHub Gist app Lists.
 
.DESCRIPTION
Show a List of all the GitHub Gist app Lists.
 
.PARAMETER GitHubUserID
User with access to the gist.
 
.PARAMETER PublicGist
Select if the list is hosted publicly.
 
.PARAMETER GitHubToken
The token for that gist.
 
.EXAMPLE
Get-PSPackageManAppList -GitHubUserID $user -PublicGist
 
#>

Function Get-PSPackageManAppList {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PSPackageMan/Get-PSPackageManAppList')]
    [OutputType([System.Object[]])]
    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 'PSPackageMan-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-PSPackageManAppList
#endregion
 
#region Get-PSPackageManInstalledApp.ps1
######## Function 4 of 11 ##################
# Function: Get-PSPackageManInstalledApp
# Module: PSPackageMan
# ModuleVersion: 0.1.3
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/09/02 19:58:36
# ModifiedOn: 2022/09/03 08:37:18
# Synopsis: This will display a list of installed apps, and their details in the repositories.
#############################################
 
<#
.SYNOPSIS
This will display a list of installed apps, and their details in the repositories.
 
.DESCRIPTION
This will display a list of installed apps, and their details in the repositories.
 
.PARAMETER PackageManager
Which package manager to query installed apps with.
 
.PARAMETER Export
Export the result to a report file. (Excel or html). Or select Host to display the object on screen.
 
.PARAMETER ReportPath
Where to save the report.
 
.EXAMPLE
Get-PSPackageManInstalledApp -PackageManager AllManagers -Export HTML -ReportPath C:\temp
 
#>

Function Get-PSPackageManInstalledApp {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PSPackageMan/Get-PSPackageManInstalledApp')]
    [OutputType([System.Object[]])]
    PARAM(
        [ValidateSet('Chocolatey', 'Winget', 'AllManagers')]
        [string]$PackageManager,
                            
        [ValidateSet('Excel', 'HTML', 'Host')]
        [string]$Export = 'Host',

        [ValidateScript( { if (Test-Path $_) { $true }
                else { New-Item -Path $_ -ItemType Directory -Force | Out-Null; $true }
            })]
        [System.IO.DirectoryInfo]$ReportPath = 'C:\Temp'
    )

    function getwinget {
        Write-Host '[Collecting]' -ForegroundColor Yellow -NoNewline
        Write-Host ' Winget Apps List' -ForegroundColor Gray 
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) BEGIN] Starting Winget extract"
            Invoke-Expression -Command "winget export -o $($env:tmp)\winget-extract.json" | Out-Null
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Winget config import"
            $importlist = Get-Content "$($env:tmp)\winget-extract.json" | ConvertFrom-Json
            $FinalList = $importlist.Sources.Packages | ForEach-Object {
                Write-Host "`t[Searching]" -ForegroundColor Yellow -NoNewline
                Write-Host " AppID: $($_.PackageIdentifier)" -ForegroundColor Gray 
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] AppID: $($_.PackageIdentifier)"        
                Search-PSPackageManApp -SearchString $_.PackageIdentifier -PackageManager Winget -MoreOptions -Exact
            }
            $FinalList
        } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
    }
    function getchoco {
        Write-Host '[Collecting]' -ForegroundColor Yellow -NoNewline
        Write-Host ' Chocolatey Apps List' -ForegroundColor Gray 
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) BEGIN] Starting Choco extract"
            $allapps = choco list --local-only --limit-output
            $finallist = foreach ($app in $allapps) {
                $appdetail = $app -split '\|'
                Write-Host "`t[Searching]" -ForegroundColor Yellow -NoNewline
                Write-Host " AppID: $($appdetail[0])" -ForegroundColor Gray 
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] AppID: $($appdetail[0])"
                Search-PSPackageManApp -SearchString $appdetail[0] -PackageManager Chocolatey -MoreOptions -Exact
            }
            $FinalList
        } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
    }

    if ($PackageManager -like 'Winget') { $Winget = getwinget}
    if ($PackageManager -like 'Chocolatey') { $Choco = getchoco}
    if ($PackageManager -like 'AllManagers') {
        $Winget = getwinget
        $Choco = getchoco
    }

    if ($Export -eq 'Excel') { 
        $ExcelOptions = @{
            Path             = $(Join-Path -Path $ReportPath -ChildPath "\InstalledApplist-$(Get-Date -Format yyyy.MM.dd-HH.mm).xlsx")
            AutoSize         = $True
            AutoFilter       = $True
            TitleBold        = $True
            TitleSize        = '28'
            TitleFillPattern = 'LightTrellis'
            TableStyle       = 'Light20'
            FreezeTopRow     = $True
            FreezePane       = '3'
        }
        if ($winget) {$winget | Export-Excel -Title 'Winget Installed App list' -WorksheetName Winget @ExcelOptions}
        if ($Choco) {$Choco | Export-Excel -Title 'Choco Installed App list' -WorksheetName Choco @ExcelOptions}
    }
    if ($Export -eq 'HTML') { 
        $script:TableSettings = @{
            Style           = 'cell-border'
            TextWhenNoData  = 'No Data to display here'
            Buttons         = 'searchBuilder', 'pdfHtml5', 'excelHtml5'
            FixedHeader     = $true
            HideFooter      = $true
            SearchHighlight = $true
            PagingStyle     = 'full'
            PagingLength    = 10
        }
        $script:SectionSettings = @{
            BackgroundColor       = 'grey'
            CanCollapse           = $true
            HeaderBackGroundColor = '#2b1200'
            HeaderTextAlignment   = 'center'
            HeaderTextColor       = '#f37000'
            HeaderTextSize        = '15'
            BorderRadius          = '20px'
        }
        $script:TableSectionSettings = @{
            BackgroundColor       = 'white'
            CanCollapse           = $true
            HeaderBackGroundColor = '#f37000'
            HeaderTextAlignment   = 'center'
            HeaderTextColor       = '#2b1200'
            HeaderTextSize        = '15'
        }
        $script:TabSettings = @{
            TextTransform = 'uppercase'
            IconBrands    = 'mix'
            TextSize      = '16' 
            TextColor     = '#00203F'
            IconSize      = '16'
            IconColor     = '#00203F'
        }

        $ReportTitle = 'Installed Apps List'
        $HeadingText = "$($ReportTitle) [$(Get-Date -Format dd) $(Get-Date -Format MMMM) $(Get-Date -Format yyyy) $(Get-Date -Format HH:mm)]"
        New-HTML -TitleText $($ReportTitle) -FilePath $(Join-Path -Path $ReportPath -ChildPath "\InstalledApplist-$(Get-Date -Format yyyy.MM.dd-HH.mm).html") {
            New-HTMLHeader {
                New-HTMLText -FontSize 20 -FontStyle normal -Color '#00203F' -Alignment left -Text $HeadingText
            }
            if ($winget) { New-HTMLTab -Name 'Winget Installed App list' @TabSettings -HtmlData {New-HTMLSection @TableSectionSettings { New-HTMLTable -DataTable $($winget) @TableSettings}}}
            if ($Choco) { New-HTMLTab -Name 'Choco Installed App list' @TabSettings -HtmlData {New-HTMLSection @TableSectionSettings { New-HTMLTable -DataTable $($Choco) @TableSettings}}}
        }
    }
    if ($Export -eq 'Host') { 
        if ($PackageManager -like 'Winget') { return $Winget}
        if ($PackageManager -like 'Chocolatey') { return $Choco}
        if ($PackageManager -like 'AllManagers') {
            return [PSCustomObject]@{
                Winget     = $Winget
                Chocolatey = $Choco
            }
        }
    }
    Write-Verbose "[$(Get-Date -Format HH:mm:ss) Complete]"
} #end Function
 
Export-ModuleMember -Function Get-PSPackageManInstalledApp
#endregion
 
#region Install-PSPackageManAppFromList.ps1
######## Function 5 of 11 ##################
# Function: Install-PSPackageManAppFromList
# Module: PSPackageMan
# ModuleVersion: 0.1.3
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/09/02 19:38:36
# ModifiedOn: 2022/09/07 17:53:34
# Synopsis: Installs the apps from the GitHub Gist List.
#############################################
 
<#
.SYNOPSIS
Installs the apps from the GitHub Gist List.
 
.DESCRIPTION
Installs the apps from the GitHub Gist List.
 
.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 LocalList
Select if the list is saved locally.
 
.PARAMETER Path
Directory where files are saved.
 
.EXAMPLE
Install-PSPackageManAppFromList -ListName twee -GitHubUserID $user -PublicGist
 
#>

Function Install-PSPackageManAppFromList {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PSPackageMan/Install-PSPackageManAppFromList')]
    PARAM(
        [Parameter(Mandatory)]
        [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(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 ($GitHubUserID) {
        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 'PSPackageMan-ConfigFile' }
        } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}
    }
    [System.Collections.Generic.List[PSCustomObject]]$AppObject = @()
    foreach ($list in $ListName) {
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) 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
            }
            $Content.Apps | Where-Object {$_ -notlike $null} | ForEach-Object {
                if ($AppObject.Exists({ -not (Compare-Object $args[0].psobject.properties.value $_.psobject.Properties.value) })) {
                    Write-Color 'Duplicate Found', " ListName: $($list)", " Name: $($_.name)" -Color Gray, DarkYellow, DarkCyan
                } else {$AppObject.Add($_)}
            }
        } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
    }


    foreach ($app in $AppObject) {
        [int]$maxlength = ($AppObject.name | Measure-Object -Property length -Maximum).Maximum
        [int]$maxPackageManagerlength = ($AppObject.PackageManager | Measure-Object -Property length -Maximum).Maximum + ($AppObject.Source | Measure-Object -Property length -Maximum).Maximum + 3
        Remove-Variable CheckInstalled -ErrorAction SilentlyContinue
        $CheckWingetPackageMan = Get-Command winget.exe -ErrorAction SilentlyContinue
        $CheckChocoPackageMan = Get-Command choco.exe -ErrorAction SilentlyContinue
        if ($app.PackageManager -like 'Winget' -and $CheckWingetPackageMan) {
            try {
                Write-Host '[Installing]' -NoNewline -ForegroundColor Yellow
                Write-Host (" {0,-$($maxPackageManagerlength)}" -f "[$($app.PackageManager)]:$($app.Source)") -ForegroundColor DarkGray -NoNewline
                Write-Host (" {0,$($maxlength)}:" -f $($app.Name) ) -ForegroundColor Cyan -NoNewline

                $CheckInstalled = Invoke-Expression -Command 'winget list' | Where-Object { $_ -match $app.id }
                if ([string]::IsNullOrEmpty($CheckInstalled)) {
                    $Command = "winget install --accept-source-agreements --accept-package-agreements --silent --id $($app.id) --source $($app.Source)" 
                    $null = Invoke-Expression -Command $Command | Where-Object { $_ }
                    if ($LASTEXITCODE -ne 0) {Write-Host ('{0} ' -f ' Failed') -ForegroundColor Red}
                    if ($LASTEXITCODE -eq 0) {Write-Host ('{0} ' -f ' Completed') -ForegroundColor Green}
                } else {
                    Write-Host ('{0} ' -f ' Already Installed') -ForegroundColor Yellow -NoNewline
                    $CheckUpgrade = Invoke-Expression -Command "winget upgrade --accept-source-agreements --accept-package-agreements --silent --id $($app.id) --source $($app.Source)"
                    if ($CheckUpgrade -like 'No applicable update found.') {Write-Host ('{0} ' -f ' No Upgrade') -ForegroundColor DarkCyan}
                    else {Write-Host ('{0} ' -f ' Upgrade Complete') -ForegroundColor DarkGreen}
                }
            } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
        } elseif ($app.PackageManager -like 'Chocolatey' -and $CheckChocoPackageMan) {
            try {
                Write-Host '[Installing]' -NoNewline -ForegroundColor Yellow
                Write-Host (" {0,-$($maxPackageManagerlength)}" -f "[$($app.PackageManager)]:$($app.Source)") -ForegroundColor DarkGray -NoNewline
                Write-Host (" {0,$($maxlength)}:" -f $($app.Name) ) -ForegroundColor Cyan -NoNewline

                $CheckInstalled = (choco list --local-only --limit-output $app.Name) -split '\|'
                $CheckOnline = (choco search $app.name --limit-output) -split '\|'
                if ([string]::IsNullOrEmpty($CheckInstalled)) {            
                    choco upgrade $($app.name) --source $($app.Source) --accept-license --limit-output -y | Out-Null
                    if ($LASTEXITCODE -ne 0) {Write-Host ('{0} ' -f ' Failed') -ForegroundColor Red}
                    if ($LASTEXITCODE -eq 0) {Write-Host ('{0} ' -f ' Completed') -ForegroundColor Green}
                } else {
                    Write-Host ('{0} ' -f ' Already Installed') -ForegroundColor Yellow -NoNewline
                    if ([version]$CheckOnline[-1] -gt [version]$CheckInstalled[-1]) {
                        choco upgrade $($app.name) --source $($app.Source) --accept-license --limit-output -y | Out-Null
                        Write-Host ('{0} ' -f ' Upgrade Complete') -ForegroundColor DarkGreen
                    } else {Write-Host ('{0} ' -f ' No Upgrade') -ForegroundColor DarkCyan}
                }
            } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
        } else {
            if (-not($CheckWingetPackageMan)) {Write-Error 'Winget is not installed.'}
            if (-not($CheckChocoPackageMan)) {Write-Error 'Chocolatey is not installed.'}
        }
    }
} #end Function
$scriptblock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if ([bool]($PSDefaultParameterValues.Keys -like '*:GitHubUserID')) {(Get-PSPackageManAppList).name }
}
Register-ArgumentCompleter -CommandName Install-PSPackageManAppFromList -ParameterName ListName -ScriptBlock $scriptblock
 
Export-ModuleMember -Function Install-PSPackageManAppFromList
#endregion
 
#region New-PSPackageManList.ps1
######## Function 6 of 11 ##################
# Function: New-PSPackageManList
# Module: PSPackageMan
# ModuleVersion: 0.1.3
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/09/02 19:51:19
# ModifiedOn: 2022/09/02 20:05:19
# Synopsis: Creates a new list file on your GitHub Gist.
#############################################
 
<#
.SYNOPSIS
Creates a new list file on your GitHub Gist.
 
.DESCRIPTION
Creates a new list file on your GitHub Gist.
 
.PARAMETER ListName
The name of the new list.
 
.PARAMETER Description
A Short description for the list.
 
.PARAMETER GitHubUserID
User with access to the gist.
 
.PARAMETER GitHubToken
The token for that gist.
 
.EXAMPLE
New-PSPackageManList -ListName drie -Description "Die derde een" -GitHubUserID $user -GitHubToken $GitHubToken
 
#>

Function New-PSPackageManList {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PSPackageMan/New-PSPackageManList')]
    [OutputType([System.Object[]])]
    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'
        Apps         = [PSCustomObject]@{}
 } | 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 'PSPackageMan-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 = 'PSPackageMan-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)" -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)" -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-PSPackageManList
#endregion
 
#region Remove-PSPackageManAppFromList.ps1
######## Function 7 of 11 ##################
# Function: Remove-PSPackageManAppFromList
# Module: PSPackageMan
# ModuleVersion: 0.1.3
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/09/02 19:54:14
# ModifiedOn: 2022/09/03 08:42:35
# Synopsis: Remove an app from one or more of the predefined GitHub Gist Lists.
#############################################
 
<#
.SYNOPSIS
Remove an app from one or more of the predefined GitHub Gist Lists.
 
.DESCRIPTION
Remove an app from one or more of the predefined GitHub Gist Lists.
 
.PARAMETER ListName
Name of the list.
 
.PARAMETER GitHubUserID
User with access to the gist.
 
.PARAMETER GitHubToken
The token for that gist.
 
.EXAMPLE
Remove-PSPackageManAppFromList -ListName twee,drie -Name speedtest -GitHubUserID $user -GitHubToken $GitHubToken
 
#>

Function Remove-PSPackageManAppFromList {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PSPackageMan/Remove-PSPackageManAppFromList')]
    [OutputType([System.Object[]])]
    PARAM(
        [Parameter(Mandatory)]
        [string]$ListName,
        [Parameter(Mandatory)]
        [string]$GitHubUserID,
        [Parameter(Mandatory)]
        [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 'PSPackageMan-ConfigFile' }
        } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}

        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) Checking Config File"
            $Content = (Invoke-WebRequest -Uri ($PRGist.files.$($ListName)).raw_url -Headers $headers).content | ConvertFrom-Json -ErrorAction Stop
        } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}

        [System.Collections.Generic.List[PSCustomObject]]$AppObject = @()
        $Content.Apps | ForEach-Object {[void]$AppObject.Add($_)}
    }
    process {
        Write-Color 'Please pick from below'
        do {
            $index = 0
            $AppObject | ForEach-Object {
                Write-Color "$($index)) ", "$($_.name)", " [$($_.PackageManager)]" -Color Yellow, Green, Cyan
                $index++ 
            }
            Write-Host 'Press Q to exit'
            $PickIndex = Read-Host 'Choose'

            if ($PickIndex.ToLower() -notlike 'q') {$AppObject.Remove($AppObject[[int]$PickIndex])}
        } while ($PickIndex.ToLower() -notlike 'q')
    }
    end {
        $Content.Apps = $AppObject | 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.$($ListName).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: $($ListName)" -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 = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if ([bool]($PSDefaultParameterValues.Keys -like '*:GitHubUserID')) {(Get-PSPackageManAppList).name }
}
Register-ArgumentCompleter -CommandName Remove-PSPackageManAppFromList -ParameterName ListName -ScriptBlock $scriptblock
 
Export-ModuleMember -Function Remove-PSPackageManAppFromList
#endregion
 
#region Remove-PSPackageManList.ps1
######## Function 8 of 11 ##################
# Function: Remove-PSPackageManList
# Module: PSPackageMan
# ModuleVersion: 0.1.3
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/09/02 19:47:58
# ModifiedOn: 2022/09/03 08:42:35
# Synopsis: Deletes a list from your GitHub Gist.
#############################################
 
<#
.SYNOPSIS
Deletes a list from your GitHub Gist.
 
.DESCRIPTION
Deletes a list from your GitHub Gist.
 
.PARAMETER ListName
The name of the list to remove.
 
.PARAMETER GitHubUserID
User with access to the gist.
 
.PARAMETER GitHubToken
The token for that gist.
 
.EXAMPLE
Remove-PSPackageManList -ListName Attempt1
 
#>

Function Remove-PSPackageManList {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PSPackageMan/Remove-PSPackageManList')]
    [OutputType([System.Object[]])]
    PARAM(
        [Parameter(Mandatory)]
        [string]$ListName,
        [Parameter(Mandatory)]
        [string]$GitHubUserID,
        [Parameter(Mandatory)]
        [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 'PSPackageMan-ConfigFile' }
    } catch {throw "Can't connect to gist:`n $($_.Exception.Message)"}


    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Create object"
    $CheckExist = $PRGist.files | Get-Member -MemberType NoteProperty | Where-Object {$_.name -like $ListName}
    if (-not([string]::IsNullOrEmpty($CheckExist))) {
        try {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESS] Remove list from Gist"
            $Body = @{}
            $files = @{}
            $Files["$($ListName)"] = $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 " $($ListName)" -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-PSPackageManAppList).name }
}
Register-ArgumentCompleter -CommandName Remove-PSPackageManList -ParameterName ListName -ScriptBlock $scriptblock
 
Export-ModuleMember -Function Remove-PSPackageManList
#endregion
 
#region Save-PSPackageManList.ps1
######## Function 9 of 11 ##################
# Function: Save-PSPackageManList
# Module: PSPackageMan
# ModuleVersion: 0.1.3
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/09/07 17:36:46
# ModifiedOn: 2022/09/07 17:55:19
# Synopsis: Saves the Gist List to the local machine
#############################################
 
<#
.SYNOPSIS
Saves the Gist List to the local machine
 
.DESCRIPTION
Saves the Gist List to the local machine
 
.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-PSPackageManList -ListName BaseApps,een,twee -Path C:\temp
 
#>

Function Save-PSPackageManList {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PSPackageMan/Save-PSPackageManList')]
    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 'PSPackageMan-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)"}
        $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-PSPackageManAppList).name }
}
Register-ArgumentCompleter -CommandName Save-PSPackageManList -ParameterName ListName -ScriptBlock $scriptblock
 
Export-ModuleMember -Function Save-PSPackageManList
#endregion
 
#region Search-PSPackageManApp.ps1
######## Function 10 of 11 ##################
# Function: Search-PSPackageManApp
# Module: PSPackageMan
# ModuleVersion: 0.1.3
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/09/02 19:30:25
# ModifiedOn: 2022/09/03 08:45:23
# Synopsis: Will search the winget and chocolatey repositories for apps
#############################################
 
<#
.SYNOPSIS
Will search the winget and chocolatey repositories for apps
 
.DESCRIPTION
Will search the winget and chocolatey repositories for apps
 
.PARAMETER SearchString
What app to search for.
 
.PARAMETER PackageManager
Which app manager to use (Chocolatey or winget)
 
.PARAMETER ChocoSource
Chocolatey source, if a personal repository is used.
 
.PARAMETER Exact
Limits the search to the exact search string.
 
.PARAMETER ShowAppDetail
Show more detail about a selected app.
 
.EXAMPLE
Search-PSPackageManApp -SearchString office -PackageManager Winget
 
#>

Function Search-PSPackageManApp {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PSPackageMan/Search-PSPackageManApp')]
    [OutputType([System.Object[]])]
    PARAM(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [Alias('Id', 'Name', 'PackageIdentifier')]
        [string[]]$SearchString,
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateSet('Chocolatey', 'Winget', 'AllManagers')]
        [string]$PackageManager,
        [string]$ChocoSource,
        [switch]$Exact,
        [switch]$ShowAppDetail
    )
    begin {
        function AppDetails {
            PARAM ($AppObject)

            Write-Color 'Please pick from below for' -Color Gray -LinesBefore 2 -LinesAfter 1
            $index = 0
            $AppObject | ForEach-Object {
                Write-Color "$($index)) ", "$($_.name)", " [$($_.version)]", " $($_.source)" -Color Yellow, Green, Cyan, Gray
                $index++ 
            }
            Write-Host ''
            [int]$AskApp = Read-Host 'Index Number for App Details'
            if ($AppObject[$AskApp].PackageManager -like 'Chocolatey') {
                [System.Collections.Generic.List[pscustomobject]]$Result = @()
                choco info $($AppObject[$AskApp].Name) --source $($AppObject[$AskApp].Source) | Where-Object { $result.add($_) }
                $ID = choco search $($AppObject[$AskApp].Name) --source $($AppObject[$AskApp].Source) --Exact --limit-output
                $descb = $Result.IndexOf(($Result | Where-Object {$_ -like '*Description:*'}))
                [PSCustomObject]@{
                    Name        = $ID.split('|')[0]
                    Version     = $ID.split('|')[1]
                    Published   = ($Result | Where-Object {$_ -like '*Published:*'}).split('|')[1].replace('Published: ', $null).trim()
                    Downloads   = ($Result | Where-Object {$_ -like '*Downloads:*'}).split('|')[0].replace('Number of Downloads: ', $null).trim()
                    Summary     = ($Result | Where-Object {$_ -like '*Summary:*'}).replace('Summary: ', $null).trim()
                    Description = (($Result[$descb..($Result.count - 2)]).replace('Description: ', $null) | Out-String).trim()
                }
            }
            if ($AppObject[$AskApp].PackageManager -like 'Winget') {
                Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] starting winget search"
                $Command = "winget show --accept-source-agreements `"$($AppObject[$AskApp].id)`""
                [System.Collections.Generic.List[pscustomobject]]$Result = @()
                Invoke-Expression -Command $Command | Where-Object { $result.add($_) }
                $descb = $Result.IndexOf(($Result | Where-Object {$_ -like 'Description:*'}))
                $desce = $Result.IndexOf(($Result | Where-Object {$_ -like 'License:*'}))
                [PSCustomObject]@{
                    Name        = ($Result | Where-Object {$_ -like 'Found*'}).replace('Found ', $null)
                    Version     = $Result | Where-Object {$_ -like 'Version:*'}
                    Publisher   = $Result | Where-Object {$_ -like 'Publisher:*'}
                    License     = $Result | Where-Object {$_ -like 'License:*'}
                    Category    = $Result | Where-Object {$_ -like 'Category:*'}
                    Pricing     = $Result | Where-Object {$_ -like 'Pricing:*'}
                    Description = (($Result[$descb..($desce - 1)]).replace('Description:', $null) | Out-String).trim()
                }
            }
        }
        function chocosearch {
            PARAM($SearchString, $ChocoSource, $Exact)

            if (Get-Command choco.exe -ErrorAction SilentlyContinue) {
                try {
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] Starting Choco search"
                    [System.Collections.Generic.List[pscustomobject]]$ChocoObject = @()
                    $source = 'chocolatey'
                    $command = "choco search $($SearchString) --limit-output --order-by-popularity"
                    if ($ChocoSource) {
                        $source = $ChocoSource
                        $command = $command + " --source $($ChocoSource)"
                    } 
                    if ($Exact) {
                        $command = $command + ' --Exact'
                    }
                    $allapps = Invoke-Expression -Command $command
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] Building choco output"
                    foreach ($app in $allapps) {
                        $appdetail = $app -split '\|'
                        $ChocoObject.add([pscustomobject]@{
                                Name           = $appdetail[0]
                                Id             = $appdetail[0]
                                version        = $appdetail[1]
                                PackageManager = 'Chocolatey'
                                source         = $Source
                            })
                    }
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] Choco done"
                    if ($ShowAppDetail) {AppDetails $ChocoObject}
                    else {$ChocoObject}
                } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
            } else {Write-Warning "Chocolatey is not installed.`nInstall it from https://chocolatey.org/install "}
        }
        function wingetsearch {
            PARAM($SearchString, $DetailedResults, $Exact)
            if (Get-Command winget.exe -ErrorAction SilentlyContinue) {
                try {
                    Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] starting winget search"
                    $Command = "winget search --accept-source-agreements `"$($SearchString)`""
                    if ($Exact) {$Command = $Command + ' --Exact'}
                    [System.Collections.Generic.List[pscustomobject]]$Result = @()
                    Invoke-Expression -Command $Command | Where-Object { $result.add($_) }
                    if ($LASTEXITCODE -ne 0) {Write-Warning "Error searching Code: $($LASTEXITCODE)"}
                    elseif ($Result -match 'No Package') {Write-Warning 'No package found matching input criteria.'}
                    else {
                        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] Building winget output"
                        [System.Collections.Generic.List[pscustomobject]]$WingetObject = @()
                        $begin = ($Result.IndexOf($Result -match '---') + 1)
                        $end = $Result.count
                        foreach ($line in ($Result[$($begin)..$($end)])) {
                            if ($line -like '*Tag*' -or $line -like '*Moniker*') {
                                $splited = $line.split(' ') | Where-Object {$_ -notlike $null}
                                $WingetObject.add([pscustomobject]@{
                                        Name           = ($splited[0..($splited.count - 4)] -join ' ')
                                        id             = $splited[-5]
                                        version        = $splited[-4]
                                        PackageManager = 'Winget'
                                        source         = $splited[-1]
                                    })
                            } else {
                                $splited = $line.split(' ') | Where-Object {$_ -notlike $null}
                                $WingetObject.add([pscustomobject]@{
                                        Name           = ($splited[0..($splited.count - 4)] -join ' ')
                                        id             = $splited[-3]
                                        version        = $splited[-2]
                                        PackageManager = 'Winget'
                                        source         = $splited[-1]
                                    })
                            }
                        }
                        Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] Winget done."
                        if ($ShowAppDetail) {AppDetails $WingetObject}
                        else {$WingetObject}                    
     }
                } catch {Write-Warning "Error: `n`tMessage:$($_.Exception.Message)"}
            } else {Write-Warning "Winget is not installed.`nInstall it from https://docs.microsoft.com/en-us/windows/package-manager/winget/ "}
        }
    }
    process {
        foreach ($search in $SearchString) {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] Starting search $($search)"
            if ($PackageManager -like 'AllManagers') {
                [PSCustomObject]@{
                    Chocolatey = chocosearch -SearchString $search -ChocoSource $ChocoSource -Exact $Exact
                    Winget     = wingetsearch -SearchString $search -DetailedResults $DetailedResults
                }
            }
            if ($PackageManager -like 'Chocolatey') {
                chocosearch -SearchString $search -ChocoSource $ChocoSource -Exact $Exact
            }
            if ($PackageManager -like 'Winget') {
                if (Get-Command winget -ErrorAction SilentlyContinue) {
                    wingetsearch -SearchString $search -DetailedResults $DetailedResults -Exact $Exact
                } else {Write-Error 'Winget is not installed. Please install and retry the search.'}
            }
        }
        Write-Verbose "[$(Get-Date -Format HH:mm:ss) DONE]"
    }
    end {}
} #end Function

Register-ArgumentCompleter -CommandName Search-PSPackageManApp -ParameterName ChocoSource -ScriptBlock {choco source --limit-output | ForEach-Object {$_.split('|')[0]}}
Register-ArgumentCompleter -CommandName Search-PSPackageManApp -ParameterName WingetSource -ScriptBlock {(winget source list) -match 'http' -split '\s+' -notmatch 'http'}
 
Export-ModuleMember -Function Search-PSPackageManApp
#endregion
 
#region Show-PSPackageManApp.ps1
######## Function 11 of 11 ##################
# Function: Show-PSPackageManApp
# Module: PSPackageMan
# ModuleVersion: 0.1.3
# Author: Pierre Smit
# Company: HTPCZA Tech
# CreatedOn: 2022/09/02 19:26:44
# ModifiedOn: 2022/09/03 08:42:51
# Synopsis: Show an app to one of the predefined GitHub Gist Lists.
#############################################
 
<#
.SYNOPSIS
Show an app to one of the predefined GitHub Gist Lists.
 
.DESCRIPTION
Show an app to one of the predefined GitHub Gist Lists.
 
.PARAMETER ListName
Name of the list.
 
.PARAMETER ShowAppDetail
Show more detail about a selected app.
 
.PARAMETER GitHubUserID
User with access to the gist.
 
.PARAMETER PublicGist
Select if the list is hosted publicly.
 
.PARAMETER GitHubToken
The token for that gist.
 
.EXAMPLE
Show-PSPackageManApp -ListName twee -GitHubUserID $user -PublicGist
 
#>

Function Show-PSPackageManApp {
    [Cmdletbinding(HelpURI = 'https://smitpi.github.io/PSPackageMan/Show-PSPackageManApp')]
    [OutputType([System.Object[]])]
    PARAM(
        [Parameter(Mandatory)]
        [string[]]$ListName,
        [switch]$ShowAppDetail,
        [Parameter(Mandatory)]
        [string]$GitHubUserID,
        [Parameter(ParameterSetName = 'Public')]
        [switch]$PublicGist,
        [Parameter(ParameterSetName = 'Private')]
        [string]$GitHubToken
    )
    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 'PSPackageMan-ConfigFile' }
    } catch {Write-Error "Can't connect to gist:`n $($_.Exception.Message)"}

    [System.Collections.Generic.List[PSCustomObject]]$AppObject = @()
    $index = 0
    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)"}

        $Content.Apps | ForEach-Object {
            $AppObject.Add([PSCustomObject]@{
                    Index          = $index
                    ListName       = $List
                    Name           = $_.Name
                    ID             = $_.id
                    PackageManager = $_.PackageManager
                    Source         = $_.Source
                })
            $index++
        }
    }
    if ($ShowAppDetail) {
        $AppObject | Format-Table -AutoSize -Wrap

        [int]$AskApp = Read-Host 'Index Number for App Details'
        if ($AppObject[$AskApp].PackageManager -like 'Chocolatey') {
            [System.Collections.Generic.List[pscustomobject]]$Result = @()
            choco info $($AppObject[$AskApp].Name) --source $($AppObject[$AskApp].Source) | Where-Object { $result.add($_) }
            $ID = choco search $($AppObject[$AskApp].Name) --source $($AppObject[$AskApp].Source) --Exact --limit-output
            $descb = $Result.IndexOf(($Result | Where-Object {$_ -like '*Description:*'}))
            [PSCustomObject]@{
                Name        = $ID.split('|')[0]
                Version     = $ID.split('|')[1]
                Published   = ($Result | Where-Object {$_ -like '*Published:*'}).split('|')[1].replace('Published: ', $null).trim()
                Downloads   = ($Result | Where-Object {$_ -like '*Downloads:*'}).split('|')[0].replace('Number of Downloads: ', $null).trim()
                Summary     = ($Result | Where-Object {$_ -like '*Summary:*'}).replace('Summary: ', $null).trim()
                Description = (($Result[$descb..($Result.count - 2)]).replace('Description: ', $null) | Out-String).trim()
            }
        }
        if ($AppObject[$AskApp].PackageManager -like 'Winget') {
            Write-Verbose "[$(Get-Date -Format HH:mm:ss) PROCESSES] starting winget search"
            $Command = "winget show --accept-source-agreements `"$($AppObject[$AskApp].id)`""
            [System.Collections.Generic.List[pscustomobject]]$Result = @()
            Invoke-Expression -Command $Command | Where-Object { $result.add($_) }
            $descb = $Result.IndexOf(($Result | Where-Object {$_ -like 'Description:*'}))
            $desce = $Result.IndexOf(($Result | Where-Object {$_ -like 'License:*'}))
            [PSCustomObject]@{
                Name        = ($Result | Where-Object {$_ -like 'Found*'}).replace('Found ', $null)
                Version     = $Result | Where-Object {$_ -like 'Version:*'}
                Publisher   = $Result | Where-Object {$_ -like 'Publisher:*'}
                License     = $Result | Where-Object {$_ -like 'License:*'}
                Category    = $Result | Where-Object {$_ -like 'Category:*'}
                Pricing     = $Result | Where-Object {$_ -like 'Pricing:*'}
                Description = (($Result[$descb..($desce - 1)]).replace('Description:', $null) | Out-String).trim()
            }
        }
    } else {$AppObject}

} #end Function
$scriptblock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    if ([bool]($PSDefaultParameterValues.Keys -like '*:GitHubUserID')) {(Get-PSPackageManAppList).name }
}
Register-ArgumentCompleter -CommandName Show-PSPackageManApp -ParameterName ListName -ScriptBlock $scriptblock
 
Export-ModuleMember -Function Show-PSPackageManApp
#endregion
 
#endregion