awsModule.psm1

function Export-Log {
    <#
 
    .SYNOPSIS
    Exports a message to a log file
 
    .DESCRIPTION
    Exports a message to a log file
 
    .PARAMETER Message
    The message to export
 
    .PARAMETER LogsPath
    The path to the logs folder
 
    .PARAMETER LogFile
    The name of the log file
 
    .EXAMPLE
    Export-Log "Ping failed, now attempting to minimize shells + run reboot script" -Logfile $LogFileName
 
    .EXAMPLE
    Export-Log "Ping failed, now attempting reboot" -Logfile $LogFileName -LogsPath "C:\Stuff\Logs"
 
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)][String]$Message,
        [Parameter(Mandatory = $false)][String]$LogFile = 'Unknown.log'

    )

    if ($LogFile -notlike '*.log') {
        $LogFile = $LogFile + '.log'
    }

    $LogFilePath = "$env:logs\$LogFile"
    if (!(Get-Item $LogFilePath -ErrorAction SilentlyContinue)) {
        try {
            New-Item -ItemType File -Path $LogFilePath | Out-Null
        }
        catch {
            New-Item -ItemType Directory -Path $env:logs
            New-Item -ItemType File -Path $LogFilePath
            Exit
        }
    }

    $Output = (Get-Date -Format "[dd/MM HH:mm:ss] [PID: $PID] ") + '[User: ' + [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + '] ' + $Message
    $Output | Out-File $LogFilePath -Append -Force
    Return $Error
}

function Restart-LLM {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)][Alias ('w')][Switch]$Window
    )
    Begin {
        New-awsEvent 100 "LLM: Restart-agent running. PID: $($PID)"
        if ($Window) {
            Start-Process pwsh -ArgumentList '-noexit -command & {restart-llm}'
            Exit
        }
        else {
            $OutputPath = 'S:\llama.cpp\output'
            $TimeStamp = Get-Date -Format '[dd/MM HH:mm:ss]'
            Write-Color "`n$TimeStamp ", "LLM loop-check module running...`n" -C Yellow, White
        }
    }
    Process {
        while ($true) {
            $j = 0
            [Int]$LineNo = ((Get-Content (Get-ChildItem $outputpath -Filter *.txt | Sort-Object lastwritetime)[-1]).IndexOf('### Response:') + 2)
            $CurrentFile = (Get-ChildItem $outputpath -Filter *.txt | Sort-Object lastwritetime)[-1]
            $Content = ((Get-Content $CurrentFile) | Select-Object -Skip $LineNo)
            $Content = $Content -replace 'Oliver|Amanda|Anton|Nikolaj|Valentin|Johannes|Frederik|Peter|Manon|Isabella|Sophie|Romy|Anna', '<NAME>'
            $Last3 = ($Content | Where-Object { $_ -notlike '' } | Select-Object -Last 3)
            $Hits = @(0, 0, 0)
            $i = 0
            foreach ($line in $Last3) {
                $Hits[$i] = ($Content | Select-String $line -SimpleMatch).Count
                $i++
            }
            if (($Hits[0] -gt 2 -and $Hits[1] -gt 2 -and $Hits[2] -gt 2) -or (($CurrentFile).Length) -gt 40000) {
                Write-Output "[$(Get-Date -Format 'HH:mm:ss')] Model is stuck, restarting"
                Get-Process | Where-Object { $_.ProcessName -like 'main' -or $_.ProcessName -like 'falcon_main' } | Select-Object -First 1 | Stop-Process
            }
            [GC]::Collect()
            while ((Get-Process | Where-Object { $_.ProcessName -like 'main' -or $_.ProcessName -like 'falcon_main' }).Count -eq 0) {
                $j++
                Start-Sleep -Seconds 30
                if ($j -gt 5) {
                    Exit
                }
            }
            Start-Sleep -Seconds 60
        }
    }
    End {
        Return
    }
}

function Set-LLM {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, Position = 0)][Alias ('m')][String]$Model
    )
    Begin {
        $ModelPath = 'S:\llama.cpp\models'
        $Models = Get-ChildItem $ModelPath -Filter *.bin -Recurse | Sort-Object Length
    }
    Process {
        if (!($Model)) {
            $Model = Menu $Models.name
        }
        else {
            $Model = ($Models | Where-Object { $_.Name -like "*$Model*" }).Name
        }
        New-awsEvent 400 "LLM: Change-model: $Model"
    }
    End {
        Return
    }
}

function Stop-LLM {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)][Alias ('i')][Switch]$Immediate
    )
    Begin {

    }
    Process {
        if (!($Immediate)) {
            New-awsEvent 402 "LLM: Setting 'stop'-flag."
        }
        else {
            Get-Process | Where-Object { $_.ProcessName -like 'main' -or $_.ProcessName -like 'falcon_main' } | Stop-Process
        }

    }

    End {

    }
}

function Out-LLM {
    [CmdletBinding()]
    param (

    )
    Begin {
        $ErrorActionPreference = 'SilentlyContinue'
        $OutputPath = 'S:\llama.cpp\output'
        $Models = @()
        $i = (Get-ChildItem "$OutputPath\Trim").Count
        $RunningLLMs = (Get-Process | Where-Object { $_.ProcessName -like 'main' -or $_.ProcessName -like 'falcon_main' }).Count
        $Texts = (Get-ChildItem $OutputPath -Filter *.txt | Sort-Object LastWriteTime -Descending | Select-Object -Skip $RunningLLMs)
        $TBD = $Texts | Where-Object { $_.Length -le 6000 }
        $Texts = $Texts | Where-Object { $_.Length -gt 6000 } | Sort-Object LastWriteTime
        $TBD | Remove-ItemSafely
        $OutputArray = @()
        $TitleArray = @()
    }
    Process {


        $i = 0

        foreach ($Text in $Texts) {
            $Content = Get-Content $Text.FullName
            $Content = $Content -join "`n"
            $Content = $Content -split '\[0m'
            $Content = $Content[-1].TrimStart()
            $Content = ($Content.Replace("`n`n`n`n", "`n"))
            $Content = ($Content.Replace("`n`n`n", "`n"))
            $Content = ($Content.Replace("`n`n", "`n"))
            $Content = ($Content.Replace("`n", "`n`n"))
            $Content = ($Content.Replace('ΓÇô', '-'))
            $Content = ($Content.Replace('ΓÇÖ', "'"))
            $Content = ($Content.Replace('ΓÇ¥', "'"))
            $Model = "$(($Text.name -split '_' | Select-Object -SkipLast 1) -join '-')"

            $FileName = "$($($Text.Name).Replace('_','-').Replace("$Model","$($Model)_$(($i).ToString().PadLeft(3, '0'))"))"
            $Content | Out-File "$OutputPath\Trim\$FileName"
            $OutputArray += "$FileName"
            $Timestamp = (Get-Date -Format 'MM-dd_HH-mm-ss')
            Move-Item $($Text.FullName) "$OutputPath\archive\$($Timestamp)_$($Text.Name)"
            $i++
        }
        foreach ($Output in $OutputArray) {
            $Title = (($Output -split '_')[-1] -split '-', 2)[1].Split('(')[0]
            $TitleArray += $Title
        }

        $TitleArray = $TitleArray | Sort-Object -Unique
        $TitleArray = $TitleArray | Where-Object { $_ -notlike '' }

        $Trim = (Get-ChildItem "$OutputPath\Trim" -Filter *.txt* | Sort-Object LastWriteTime)


        foreach ($TT in $Trim) {
            $Model = "$(($TT.name -split '_')[0])"
            if ($Models.Name -notcontains $Model) {
                $ModelObject = [PSCustomObject]@{
                    Name   = $Model
                    Number = 1
                }
                $Models += $ModelObject
            }

            $TitleNo = (($Models | Where-Object { $_.Name -like $Model }).Number | Out-String).Trim().PadLeft(3, '0')
            $Title = ((($TT.name).Split('(')[0]).Split('_')[-1]).Split('-', 2)[1].Replace('.txt', '')
            $NewName = "$Model" + '_' + "$TitleNo" + '_' + "$Title" + '.txt'

            if ($Title -notin $TitleArray) {
                $MovePath = "$OutputPath\trim\Unread"
            }
            else {
                $MovePath = "$OutputPath\trim"
            }
            while (Test-Path "$MovePath\$NewName") {
                $Models | Where-Object { $_.Name -like $Model } | ForEach-Object { $_.Number++ }
                $TitleNo = (($Models | Where-Object { $_.Name -like $Model }).Number | Out-String).Trim().PadLeft(3, '0')
                $NewName = "$Model" + '_' + "$TitleNo" + '_' + "$Title" + '.txt'
            }
            if ($TT.Name -in $OutputArray) {
                $OutputArray[$($OutputArray.IndexOf($TT.Name))] = $NewName
            }
            Move-Item $TT.FullName -Destination "$MovePath\$NewName"
        }
    }

    End {
        Write-Color "`n$($Texts.count) files formatted and moved to $OutputPath\Trim:`n" -C Yellow
        Return $OutputArray
    }
}

function Update-LLM {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)][Alias ('fa', 'fal')][Switch]$Falcon,
        [Parameter(Mandatory = $false)][Alias ('ll', 'lla')][Switch]$Llama,
        [Parameter(Mandatory = $false)][Alias ('f')][Switch]$Force
    )
    Begin {
        if ($Falcon) {
            $UpdateObj = [PSCustomObject]@{
                Falcon = $true
                Llama  = $false
            }
        }
        elseif ($Llama) {
            $UpdateObj = [PSCustomObject]@{
                Falcon = $false
                Llama  = $true
            }
        }
        else {
            $UpdateObj = [PSCustomObject]@{
                Falcon = $true
                Llama  = $true
            }
        }
    }
    Process {
        while ($UpdateObj.Llama -or $UpdateObj.Falcon) {

            if ($UpdateObj.Falcon) {
                $Foldername = 'ggllm.cpp'
                $GitClone = 'https://github.com/cmp-nct/ggllm.cpp'
                $cmake = { cmake -DLLAMA_CUBLAS=1 -DCUDAToolkit_ROOT="C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA" .. }
                $UpdateObj.Falcon = $false
            }
            elseif ($UpdateObj.Llama) {
                $Foldername = 'llama.cpp'
                $GitClone = 'https://github.com/ggerganov/llama.cpp'
                $cmake = { cmake .. -DLLAMA_CUBLAS=ON }
                $UpdateObj.Llama = $false
            }

            if ($Force) {
                if (Test-Path "$($Foldername)_old") {
                    Rename-Item "$($Foldername)_old" -NewName "$($Foldername)_old_old"
                }
                Rename-Item "P:\llama\$($Foldername)_backup" -NewName "$($Foldername)_old"
                Copy-Item "P:\llama\$Foldername" "P:\llama\$($Foldername)_backup"

                while (Test-Path "P:\llama\$Foldername") {
                    try {
                        Remove-Item "P:\llama\$Foldername" -Recurse -Force -ErrorAction Stop
                    }
                    catch {
                        Write-Color "`n $($Foldername) is currently in use.", "`n`n Please close all instances of it and press Enter to try again.`n" -C Red, Yellow
                        Read-Host
                    }
                }

                Set-Location 'P:\llama'
                git clone $GitClone
                Set-Location "P:\llama\$Foldername"
            }
            else {
                Set-Location "P:\llama\$Foldername"
                git fetch
                $GitStatus = git status -uno
            }

            if (($GitStatus -like '*Your branch is behind*') -or ($Force)) {

                if (!($Force)) {
                    if (Test-Path "$($Foldername)_old") {
                        Rename-Item "$($Foldername)_old" -NewName "$($Foldername)_old_old"
                    }
                    Rename-Item "P:\llama\$($Foldername)_backup" -NewName "$($Foldername)_old"
                    Copy-Item "P:\llama\$Foldername" "P:\llama\$($Foldername)_backup"
                    git pull
                    Remove-Item 'Build' -Recurse -Force
                }

                New-Item -ItemType Directory -Path "P:\llama\$($Foldername)\build"
                Set-Location "P:\llama\$($Foldername)\build"
                Invoke-Command -ScriptBlock $cmake
                Invoke-Command -ScriptBlock { cmake --build . --config Release }

                $OldFolders = Get-ChildItem 'P:\llama' -Directory -Filter "$($Foldername)_old*" | Sort-Object CreationTime

                foreach ($OldFolder in $OldFolders) {
                    if ($OldFolder.CreationTime -lt (Get-Date).AddDays(-7)) {
                        $OldFolder | Remove-ItemSafely
                    }
                    else {
                        $i = 0
                        while (Test-Path "P:\llama\$($Foldername)_old$i") {
                            $i++
                        }
                        Rename-Item $OldFolder -NewName "$($Foldername)_old$i"
                    }
                }
            }
            else {
                Write-Color "`nNo updates available for $Foldername`n" -C Yellow
            }
        }
        Set-Location 'P:\llama'
    }
    End {

    }
}

# function Use-LLM.old {
# [CmdletBinding()]
# param (
# [Parameter(Mandatory = $false, Position = 0)][Alias ('p')][String]$Prompt,
# [Parameter(Mandatory = $false, Position = 1)][Alias ('m')][String]$ModelName,
# [Parameter(Mandatory = $false)][Alias ('n')][String]$Name,
# [Parameter(Mandatory = $false)][Alias ('th')][Int]$Threads = 4,
# [Parameter(Mandatory = $false)][Alias ('b')][Int]$Batchsize = 512,
# [Parameter(Mandatory = $false)][Alias ('c', 'ctx')][Int]$Context,
# [Parameter(Mandatory = $false)][Alias ('t', 'temp')]$Temperature,
# [Parameter(Mandatory = $false)][Alias ('r')][Switch]$Rotate,
# # [Parameter(Mandatory = $false)][Alias ("i")][Switch]$Interactive,
# [Parameter(Mandatory = $false)][Switch]$Test,
# [Parameter(Mandatory = $false)][Alias ('u')][Switch]$Update,
# [Parameter(Mandatory = $false)][Alias ('nr')][Switch]$NoRope

# )
# Begin {
# $SwitchParams = @(
# 'Rotate',
# 'Interactive',
# 'Test',
# 'Update')

# $TrueVars = Get-Variable | Where-Object { $_.Value -like 'True' }

# $OutputPath = 'P:\llama\output'
# $AllModels = @()
# $Models = @()
# $LastModels = (((Get-EventLog aws -Source LLM -InstanceId 400 | Where-Object { $_.Message -like '*LLM: Models in current rotation:*' } | Select-Object -First 1).message -split 'LLM: Models in current rotation: ')[1] -split ';')
# if (!$NoRope) {
# $Rope = '--rope-freq-base 32000 --rope-freq-scale 1'
# }
# else {
# $Rope = ''
# }
# foreach ($ModelFile in (Get-ChildItem 'P:\llama\models' -Filter *.bin -Recurse)) {
# $Obj = [PSCustomObject]@{
# Name = $(($ModelFile.Name.Split('.'))[0])
# NameShort = $(($ModelFile.Name -split "-$((((($ModelFile.Name) -Split '-') | Where-Object {$_ -like '*[0-9]B*'}).split('.') | Where-Object {$_ -like '*[0-9]B*'}))")[0])
# WeightsB = [Int]$((((($ModelFile.Name) -Split '-') | Where-Object { $_ -like '*[0-9]B*' }).split('.') | Where-Object { $_ -like '*[0-9]B*' }).TrimEnd('B', 'b'))
# Weights = $((((($ModelFile.Name) -Split '-') | Where-Object { $_ -like '*[0-9]B*' }).split('.') | Where-Object { $_ -like '*[0-9]B*' }))
# Type = 'Type'
# GGv = $((($ModelFile.Name).Split('.')[-3]))
# Quant = $((($ModelFile.Name).Split('.')[-2]))
# SizeGB = $([Double]('{0:N2}' -f (($ModelFile | Measure-Object -Property length -Sum).sum / 1GB)))
# Size = $('{0:N2} GB' -f (($ModelFile | Measure-Object -Property length -Sum).sum / 1GB))
# FullName = $($ModelFile.FullName)
# FileName = $($ModelFile.Name)
# }
# if ($Obj.FileName -in $LastModels) {
# $Obj | Add-Member -MemberType NoteProperty -Name 'PrevRun' -Value $true
# }
# if ($Obj.Name -like '*falcon*') {
# $Obj.Type = 'Falcon'
# }
# elseif ($Obj.Name -like '*superhot*') {
# $Obj.Type = 'Kobold'
# }
# else {
# $Obj.Type = 'Llama'
# }
# $AllModels += $Obj
# }

# if ($Update) {
# $UpdateObj = [PSCustomObject]@{
# Falcon = $true
# Llama = $true
# }
# }

# if ($Rotate) {
# $ModelsChosen = ($AllModels | Sort-Object WeightsB, NameShort, SizeGB | Select-Object Weights, Name, Type, GGv, Quant, Size, PrevRun ) | Out-ConsoleGridView -Title 'Select models to rotate between'
# foreach ($ModelObj in $ModelsChosen) {
# $Models += $AllModels | Where-Object { $_.Name -like "$($ModelObj.Name)" -and $_.Type -like "$($ModelObj.Type)" -and $_.GGv -like "$($ModelObj.GGv)" -and $_.Quant -like "$($ModelObj.Quant)" -and $_.Size -like "$($ModelObj.Size)" }
# }
# New-awsEvent 400 "LLM: Models in current rotation: $($Models.FileName -join ';')"
# }
# else {
# $Models = ($AllModels | Sort-Object WeightsB, NameShort, SizeGB)
# }

# if (!($Model)) {
# $Model = ($Models | Select-Object Weights, Name, Type, GGv, Quant, Size ) | Out-ConsoleGridView -Title 'Select model to use' -OutputMode Single
# $Model = $Models | Where-Object { $_.Name -like "$($Model.Name)" -and $_.Type -like "$($Model.Type)" -and $_.GGv -like "$($Model.GGv)" -and $_.Quant -like "$($Model.Quant)" -and $_.Size -like "$($Model.Size)" }
# }
# Write-Color "`nWhat ", 'filename', " should be appended to the output? (leave blank for ""$(((Get-EventLog aws -Source LLM -InstanceId 401 | Select-Object -First 1).message -split 'Output: ')[-1].Trim())"")" -C White, Yellow, White
# $Name = Read-Host 'Input filename'

# if (!$Context) {
# if ($Rotate) {
# Write-Color "`n`nWhat ", 'context length', ' should be used for Falcon? (leave blank for 8192)' -C White, Yellow, White
# $ContextFalcon = Read-Host 'Input context length between 0 and 8192'

# Write-Color "`n`nWhat ", 'context length', ' should be used for non-Falcon? (leave blank for 2048)' -C White, Yellow, White
# $Context = Read-Host 'Input context length between 0 and 2048'
# }
# else {
# Write-Color "`n`nWhat ", 'context length', ' should be used? (leave blank for default (8192 for Falcon, 2048 for non-Falcon))' -C White, Yellow, White
# $Context = Read-Host 'Input context length between 0 and 8192'
# $ContextFalcon = $Context
# }
# if (!($ContextFalcon)) {
# $ContextFalcon = 8192
# }
# if (!($Context)) {
# $Context = 2048
# }
# }
# $NGLArray = Import-Csv 'P:\llama\ngl.csv'
# $IndexNo = ($NGLArray.model).IndexOf($Model.filename)
# if ($IndexNo -eq -1) {
# $modelngl = [PSCustomObject]@{
# Model = $Model.FileName
# NGL = 0
# }
# $NGLArray += $modelngl
# $IndexNo = ($NGLArray.model).IndexOf($Model.filename)
# }
# [Int]$ngl = $NGLArray[$IndexNo].NGL

# Write-Color "`n`nHow many layers should be offloaded to GPU? (Leave blank to use default/last used [", "$ngl", '])' -C White, Yellow, White
# [Int]$nglChoice = Read-Host 'Input number of layers'

# if ($nglChoice) {
# $ngl = $nglChoice
# }

# if (!($ngl)) {
# if ($Model.Weights -like '40B') {
# $ngl = 30
# }
# elseif ($Model.Weights -like '7B') {
# $ngl = 50
# }
# elseif ($Model.Weights -like '65B') {
# $ngl = 6
# }
# elseif ($Model.Weights -like '3[0,3]B') {
# $ngl = 13
# }
# elseif ($Model.Weights -like '13B') {
# $ngl = 30
# }
# elseif ($Model.Weights -like '70B') {
# $ngl = 5
# }
# }
# $NGLArray[$IndexNo].NGL = $ngl

# $NGLArray | Export-Csv 'P:\llama\ngl.csv' -Force

# if (!$Temperature) {
# Write-Color "`n`nWhat temperature should be used? (leave blank for default [", '0.7', '])' -C White, Yellow, White
# $Temperature = Read-Host 'Input temperature'
# if (!($Temperature)) {
# $Temperature = 0.7
# }
# }
# if (Get-Process | Where-Object { $_.ProcessName -like 'main' -or $_.ProcessName -like 'falcon_main' }) {
# $RunNow = 'n'
# }
# else {
# $RunNow = 'y'
# }

# if (!($Name)) {
# $Name = ((Get-EventLog aws -Source LLM -InstanceId 401 | Select-Object -First 1).message -split 'Output: ')[-1].Trim()
# }
# New-awsEvent 402 "LLM: Setting 'stop'-flag."
# $EventLog = (Get-EventLog aws -InstanceId 100 -Source LLM | Select-Object -First 3).Message | ForEach-Object { $_.Split('PID: ')[-1].Trim() }
# if (!($Test) -and ((Get-Process -Id $EventLog -ErrorAction SilentlyContinue).Count -eq 0)) {
# Start-Process pwsh -ArgumentList '-noexit -WindowStyle Minimized -command & {restart-llm}'
# }
# Clear-Host
# }

# Process {
# [String]$TimeStamp = Get-Date -Format '[dd/MM HH:mm:ss]'
# Write-Color "`n$TimeStamp ", "Starting inference with the following parameters:`n" -C Yellow, White

# foreach ($Param in $SwitchParams) {
# if ($Param -in $TrueVars.Name) {
# Write-Color "`t$($Param): True" -C Green
# }
# }
# Write-Output `n

# if (($RunNow -like 'y') -or ($Interactive)) {
# New-awsEvent 401 "LLM: Setting 'start'-flag. Output: $Name"
# }
# else {
# while ((Get-EventLog aws -Source LLM -InstanceId 401, 402 | Select-Object -First 1).InstanceID -ne 401) {
# Write-Color "`rLLM is currently running. Waiting for current run to finish. " -NoNewLine
# Write-Color "`rLLM is currently running. Waiting for current run to finish." -NoNewLine
# Start-Sleep -Seconds 1
# $i = 0
# while ($i -lt 10) {
# Write-Color '.' -NoNewLine
# Start-Sleep -Seconds 1
# $i++
# }
# if (!(Get-Process | Where-Object { $_.ProcessName -like 'main' -or $_.ProcessName -like 'falcon_main' })) {
# New-awsEvent 401 "LLM: Setting 'start'-flag. Output: $Name"
# }
# }
# }

# if ($UpdateObj.Llama -or $UpdateObj.Falcon) {

# if (($Model.Type -like 'Falcon') -and ($UpdateObj.Falcon -eq $true)) {
# Update-LLM -fa
# $UpdateObj.Falcon = $false
# }
# elseif (($Model.Type -like 'Llama') -and ($UpdateObj.Llama -eq $true)) {
# Update-LLM -ll
# $UpdateObj.Llama = $false
# }
# }

# while ((Get-EventLog aws -Source LLM -InstanceId 401, 402 | Select-Object -First 1).InstanceID -ne 402) {
# $Prompt = "$((Get-Content 'P:\llama\prompt.txt') -join "`n")`n"
# if ($Model.Name -like '*h2ogpt*') {
# $Prompt = ((("<|prompt|>`n" + ((("$((Get-Content 'P:\llama\prompt.txt') -join "`n")`n") -split '### Instruction:')[1].Trim()) -split '### Response:')[0].Trim()) + "`n<|endoftext|>`n<|answer|>")
# }
# if (!($ngl)) {
# $NGLArray = Import-Csv 'P:\llama\ngl.csv'
# if ($NGLArray.model -notcontains $Model.filename) {
# if ($Model.Weights -like '40B') {
# $ngl = 30
# }
# elseif ($Model.Weights -like '7B') {
# $ngl = 50
# }
# elseif ($Model.Weights -like '65B') {
# $ngl = 6
# }
# elseif ($Model.Weights -like '3[0,3]B') {
# $ngl = 13
# }
# elseif ($Model.Weights -like '13B') {
# $ngl = 30
# }
# elseif ($Model.Weights -like '70B') {
# $ngl = 5
# }
# $modelngl = [PSCustomObject]@{
# Model = $Model.FileName
# NGL = $ngl
# }
# $NGLArray += $modelngl
# }
# $IndexNo = ($NGLArray.model).IndexOf($Model.filename)
# $ngl = $NGLArray[$IndexNo].NGL
# }

# if ($Model.Weights -like '70B') {
# $gqa = '--gqa 8'
# }
# else {
# $gqa = ''
# }

# $ErrorActionPreference = 'Stop'

# try {
# [Int]$Ctx = $Context
# [Int]$CtxFalcon = $ContextFalcon

# }
# catch {
# [Int]$Ctx = 2048
# [Int]$CtxFalcon = 8192
# }

# $ErrorActionPreference = 'Continue'

# $i = 1
# $File = "$OutputPath\$(($Model.FileName -split '\.')[0,-2] -join '_')_$Name"
# while (Test-Path "$File($i).txt") {
# $i++
# }
# $LastUsed = "main.exe -t $Threads -p '$Prompt' --color -c $Ctx -b $Batchsize $gqa $Rope --temp $Temperature -ngl $ngl --no-penalize-nl --repeat-last-n 128 --keep -1 --mlock -m '$($Model.Fullname)' | Tee-Object -File '$File($i).txt'"
# $LastUsed | Out-File 'P:\llama\lastused.txt' -Force

# if (!($Interactive)) {
# [GC]::Collect()
# [String]$TimeStamp = Get-Date -Format '[dd/MM HH:mm:ss]'
# Write-Color "`n", "Starting inference of the following model:`n" -C Yellow, White
# Write-Color "$TimeStamp ", "$($Model.FileName)`n" -C Gray, Green
# if ($Model.Type -like 'Falcon') {
# if ($Test) {
# $LastUsed = ". $env:falcon\falcon_main.exe -t $Threads -p '$Prompt' --color -c $Ctx -b $Batchsize $gqa $Rope --temp $Temperature -ngl $ngl --no-penalize-nl --repeat-last-n 128 --keep -1 --mlock -m '$($Model.Fullname)'"
# $LastUsed | Out-File 'P:\llama\lastused.txt' -Force
# . $env:falcon\falcon_main.exe -t $Threads -p "$Prompt" --color -c $Ctx -b $Batchsize $gqa $Rope --temp $Temperature -ngl $ngl --no-penalize-nl --repeat-last-n 128 --keep -1 --mlock -m "$($Model.Fullname)"
# }
# else {
# $LastUsed = ". $env:falcon\falcon_main.exe -t $Threads -p '$Prompt' --color -c $Ctx -b $Batchsize $gqa $Rope --temp $Temperature -ngl $ngl --no-penalize-nl --repeat-last-n 128 --keep -1 --mlock -m '$($Model.Fullname)' | Tee-Object -File '$File($i).txt'"
# $LastUsed | Out-File 'P:\llama\lastused.txt' -Force
# . $env:falcon\falcon_main.exe -t $Threads -p "$Prompt" --color -c $Ctx -b $Batchsize $gqa $Rope --temp $Temperature -ngl $ngl --no-penalize-nl --repeat-last-n 128 --keep -1 --mlock -m "$($Model.Fullname)" | Tee-Object -File "$File($i).txt"
# }
# }
# else {
# if ($Test) {
# $LastUsed = "main.exe -t $Threads -p '$Prompt' --color -c $Ctx -b $Batchsize $gqa $Rope --temp $Temperature -ngl $ngl --no-penalize-nl --repeat-last-n 128 --keep -1 --mlock -m '$($Model.Fullname)'"
# $LastUsed | Out-File 'P:\llama\lastused.txt' -Force
# main.exe -t $Threads -p "$Prompt" --color -c $Ctx -b $Batchsize $gqa $Rope --temp $Temperature -ngl $ngl --no-penalize-nl --repeat-last-n 128 --keep -1 --mlock -m "$($Model.Fullname)"
# }
# else {
# $LastUsed = "main.exe -t $Threads -p '$Prompt' --color -c $Ctx -b $Batchsize $gqa $Rope --temp $Temperature -ngl $ngl --no-penalize-nl --repeat-last-n 128 --keep -1 --mlock -m '$($Model.Fullname)' | Tee-Object -File '$File($i).txt'"
# $LastUsed | Out-File 'P:\llama\lastused.txt' -Force
# main.exe -t $Threads -p "$Prompt" --color -c $Ctx -b $Batchsize $gqa $Rope --temp $Temperature -ngl $ngl --no-penalize-nl --repeat-last-n 128 --keep -1 --mlock -m "$($Model.Fullname)" | Tee-Object -File "$File($i).txt"
# }
# }
# if (((Get-EventLog aws -Source LLM -InstanceId 400 | Select-Object -First 1).Message -like 'LLM: Change-model:*') -and ((Get-EventLog aws -Source LLM -InstanceId 401, 402 | Select-Object -First 1).InstanceID -ne 402)) {
# $Model = $Models | Where-Object { $_.FileName -like "$(((Get-EventLog aws -Source LLM -InstanceId 400 | Select-Object -First 1).Message -split 'Change-model: ')[-1].Trim())" }
# New-awsEvent 400 'LLM: Model changed for next inference'
# }
# elseif ($Rotate) {
# if ((($Models.FileName).IndexOf($Model.FileName) + 1) -ge $Models.Count) {
# $Model = $Models[0]
# }
# else {
# $Model = $Models[(($Models.FileName).IndexOf($Model.FileName) + 1)]
# }
# }
# }
# else {
# [GC]::Collect()
# if ($Model.Type -like 'Falcon') {
# . $env:falcon\falcon_main.exe --repeat-last-n 128 --keep -1 --mlock --no-penalize-nl -b $Batchsize -t $Threads -ngl $ngl --color -c $CtxFalcon --temp 0.6 --interactive-first -m "$($Model.Fullname)"
# }
# else {
# main.exe --repeat-last-n 128 --keep -1 --mlock --no-penalize-nl -b $Batchsize -t $Threads -ngl $ngl --color -c $Ctx --temp 0.7 --interactive-first -m "$($Model.Fullname)"
# }
# New-awsEvent 402 "LLM: Setting 'stop'-flag."
# }
# }
# New-awsEvent 401 "LLM: Setting 'start'-flag. Output: $Name"
# }
# End {
# Return
# }
# }

function Use-LLM {
    [CmdletBinding('Use-LLM')]
    param (
        [Parameter(Mandatory = $false, Position = 0)][Alias ('p')][String]$Prompt,
        [Parameter(Mandatory = $false, Position = 1)][Alias ('m')][String]$ModelName,
        [Parameter(Mandatory = $false)][Alias ('r')][Switch]$Repeat,
        [Parameter(Mandatory = $false)][Alias ('th')][Int]$Threads = 4,
        [Parameter(Mandatory = $false)][Alias ('b')][Int]$Batchsize = 512,
        [Parameter(Mandatory = $false)][Alias ('c', 'ctx')][Int]$Context = 2048,
        [Parameter(Mandatory = $false)][Alias ('t', 'temp')]$Temperature = 0.7,
        [Parameter(Mandatory = $false)][Alias ('n', 'ngl', 'l')][Int]$Layers
    )
    Begin {
        $AllModels = @()
        $Models = @()
        $LastUsed = Get-Content 'P:\llama\lastused.txt' -ErrorAction SilentlyContinue

        foreach ($ModelFile in (Get-ChildItem 'P:\llama\models' -Filter *.gguf -Recurse)) {
            $Obj = [PSCustomObject]@{
                WeightsB = [Double]($(((($ModelFile.Name) -Split '-') | Where-Object { $_ -match '[0-9]b' }) -Split 'b')[0])
                Weights  = ($((($ModelFile.Name) -Split '-') | Where-Object { $_ -match '[0-9]B' }) -split '(?<=b)')[0]
                Name     = ($ModelFile.Name -split $($((($ModelFile.Name) -Split '-') | Where-Object { $_ -match '[0-9]b' })))[0].TrimEnd('-')
                NameLong = "$(($ModelFile.Name -split $($((($ModelFile.Name) -Split '-') | Where-Object { $_ -match '[0-9]b' })))[0].TrimEnd('-')) [$($((($ModelFile.Name) -Split '-') | Where-Object { $_ -match '[0-9]b' }))]"
                Quant    = $((($ModelFile.Name).Split('.')[-2]))
                Size     = $('{0:N2} GB' -f (($ModelFile | Measure-Object -Property length -Sum).sum / 1GB))
                FullName = $($ModelFile.FullName)
                FileName = $($ModelFile.Name)
                PrevRun  = $false
            }
            $AllModels += $Obj
        }
        $AllModels | Where-Object { $_.FileName -eq $LastUsed } | ForEach-Object { $_.PrevRun = $true }
        $Models = ($AllModels | Sort-Object -Property @{e = { $_.PrevRun }; Ascending = $false }, Name, WeightsB, Quant, Size)

        if (!($ModelName)) {
            $Model = ($Models | Select-Object Name, Weights, Quant, Size ) | Out-ConsoleGridView -Title 'Select model to use' -OutputMode Single
            $Model = $Models | Where-Object { $_.Name -eq "$($Model.Name)" -and $_.Quant -eq "$($Model.Quant)" -and $_.Size -eq "$($Model.Size)" -and $_.Weights -eq "$($Model.Weights)" }
        }

        $Model.FileName | Out-File 'P:\llama\lastused.txt' -Force

        if ($Model.WeightsB -lt 10) {
            $DefaultNgl = 50
        }
        elseif ($Model.WeightsB -lt 41) {
            $DefaultNgl = 18
        }
        else {
            $DefaultNgl = 8
        }

        $NGLArray = Import-Csv 'P:\llama\models.csv'
        $IndexNo = ($NGLArray.model).IndexOf($Model.filename)
        if ($IndexNo -eq -1) {
            $modelngl = [PSCustomObject]@{
                Model = $Model.FileName
                NGL   = $DefaultNgl
            }
            $NGLArray += $modelngl
            $IndexNo = ($NGLArray.model).IndexOf($Model.filename)
        }
        [Int]$ngl = $NGLArray[$IndexNo].NGL

        if ($Layers) {
            $ngl = $Layers
        }

        $NGLArray[$IndexNo].NGL = $ngl

        $NGLArray | Export-Csv 'P:\llama\models.csv' -Force
    }

    Process {
        Do {
            if (!$Prompt) {
                Try {
                    $Prompt = Get-Content 'P:\llama\prompt.txt' -ErrorAction Stop
                }
                Catch {
                    Return "`nNo prompt file found"
                }
            }
            $PromptFormat = (Import-Csv 'P:\llama\promptformats.csv' | Where-Object { $_.Model -match $Model.Name }).Prompt

            if ($PromptFormat -and $Prompt -notmatch $PromptFormat) {
                $Prompt = "$($PromptFormat.Replace('<REPLACE>', $Prompt))"
            }

            [String]$TimeStamp = Get-Date -Format '[dd/MM HH:mm:ss]'
            Write-Color "`n$TimeStamp ", "Starting inference with the following parameters:`n" -C Yellow, White
            Write-Color "Model: ", "$($Model.NameLong)", "`nThreads: ", "$Threads", "`nBatchsize: ", "$Batchsize", "`nContext: ", "$Context", "`nTemperature: ", "$Temperature", "`nNGL: ", "$ngl" -C White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow
            Write-Output `n
            [String]$FileTimeStamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"

            P:\llama\llama.cpp\main.exe -t $Threads -p "$Prompt" --color -c $Context -b $Batchsize --temp $Temperature -ngl $ngl --repeat-last-n 128 --keep -1 --mlock --no-penalize-nl -m "$($Model.Fullname)" | Tee-Object -File "P:\llama\Output\$($Model.Name)_($FileTimeStamp).txt"
        } while ($Repeat)
    }
    End {
        Return
    }
}

function Test-LLM {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)][Alias ('a')][Switch]$Average,
        [Parameter(Mandatory = $false)][Alias ('r')][Switch]$Recent
    )
    Begin {
        $backups = Get-ChildItem -Path S:\llama.cpp\ -Filter 'backup_out*.txt'

        foreach ($backup in $backups) {
            if ($backup.CreationTime -lt (Get-Date).AddDays(-7)) {
                $backup | Remove-ItemSafely
            }
        }
        $files = Get-ChildItem -Path S:\llama.cpp\ -Filter out*.txt
        Copy-Item 'S:\llama.cpp\Results.csv' 'S:\llama.cpp\Results_backup.csv' -Force
        Copy-Item 'S:\llama.cpp\Averages.csv' 'S:\llama.cpp\Averages_backup.csv' -Force
        $i = 0
        $Results = @()
    }
    Process {
        if (!($Recent)) {
            foreach ($file in $files) {

                $ModelArray = @()
                $asd = (Get-Content $File) -join "`n"
                $asd = ($asd -split 'Starting inference of the following model:') | Where-Object { $_ -like '*_print_timings:*' }

                foreach ($as in $asd) {
                    $split = ($as -split "`n" | Where-Object { $_ -like '*_print_timings:*' } )
                    $StartTime = ((($as -split "`n" | Where-Object { $_ -like '*.bin' })[0]) -split '] ')[0].TrimStart('[')
                    $StartTime = [DateTime]::ParseExact($StartTime, 'dd/MM HH:mm:ss', ([System.Globalization.CultureInfo]::InvariantCulture))
                    $model = ((($as -split "`n" | Where-Object { $_ -like '*.bin' })[0]) -split '] ')[1]
                    if (($split) -and ($model -like '*.bin')) {
                        if ($model -like '*\*') {
                            $model = ($model -split '\\')[-1]
                        }
                        $Weights = ($Model -split '([0-9][0-9]B)')[1]
                        if (!($Weights)) {
                            $Weights = ('0' + ($Model -split '([0-9]B)')[1])
                        }

                        [Float]$Time = (($split[-2] -split '\(')[-1] -split ' ms')[0].Trim()

                        $Obj = [PSCustomObject]@{
                            Model = "$($Weights)_$((($Model).Trim('.bin')).Trim())"
                            Time  = $Time
                            Date  = $StartTime
                        }

                        $ModelArray += $Obj
                    }
                }

                $Models = $modelarray.model | Sort-Object | Get-Unique

                foreach ($Model in $Models) {
                    $Results += $ModelArray | Where-Object { $_.Model -like $Model }
                }

                while (Test-Path "S:\llama.cpp\backup_out$($i).txt") {
                    $i++
                }
                Copy-Item $File.FullName "S:\llama.cpp\backup_out$($i).txt" -Force
                Remove-ItemSafely -Path $File.FullName
            }
        }
    }
    End {
        if (!($Recent)) {
            $Results = $Results | Sort-Object Model, Time -Unique -Descending
            $Results | Export-Csv -Path S:\llama.cpp\Results.csv -NoTypeInformation -Append
        }

        $CSV = Import-Csv S:\llama.cpp\Results.csv | Sort-Object Model, Time -Unique -Descending

        if ($Recent) {
            $Avg = foreach ($Model in ($CSV.Model | Sort-Object -Unique)) {
                $CSV | Where-Object { $_.Model -like $Model } | Measure-Object -Property Time -Average | Select-Object @{Name = 'Model'; Expression = { $Model } }, @{Name = 'Average'; Expression = { $_.Average } }
            }
        }
        else {
            $Avg = foreach ($Model in ($CSV.Model | Sort-Object -Unique)) {
                $CSV | Where-Object { $_.Model -like $Model -and $_.Date -gt ((Get-Date).AddMinutes(-1)) } | Measure-Object -Property Time -Average | Select-Object @{Name = 'Model'; Expression = { $Model } }, @{Name = 'Average'; Expression = { $_.Average } }
            }
        }

        $Avg | ForEach-Object { $_.Average = [Math]::Round($_.Average, 2) }
        $Avg = $Avg | Sort-Object Average -Descending

        $CSV | Select-Object Model, Time, Date | Sort-Object Model, Time -Unique -Descending | Export-Csv -Path S:\llama.cpp\Results.csv -NoTypeInformation -Force
        $Avg | Sort-Object Average -Descending | Export-Csv -Path S:\llama.cpp\Averages.csv -NoTypeInformation -Force

        if ($Recent) {
            $i = 0
            $NameLengths = @()
            $RecentAverages = @()
            foreach ($Model in ($CSV.Model | Sort-Object -Unique -Descending)) {
                $NameLengths += $Model.Length
            }
            [Int]$Length = $NameLengths | Sort-Object -Descending | Select-Object -First 1

            foreach ($Model in ($CSV.Model | Sort-Object -Unique -Descending)) {
                $Spaces = (' ' * ($Length - ($Model.Length)))
                $Recent5 = $CSV | Where-Object { $_.Model -like $Model } | Sort-Object Date, Time -Descending | Select-Object -First 5
                $Recent5 | ForEach-Object { $_.Time = "$($_.Time) " ; $_.Model = "$($_.Model)$Spaces " ; $_.Date = "$((($_.Date) -split ' ')[0]) " }
                $5Avg = $Recent5 | Measure-Object -Property Time -Average | Select-Object @{Name = 'Model'; Expression = { $Model } }, @{Name = 'Average'; Expression = { $_.Average } }
                $5Avg | ForEach-Object { $_.Average = [Math]::Round($_.Average, 2) }
                $5Avg = $5Avg | Sort-Object Average -Descending
                $ModelAvg = $5Avg.Average
                $AvgObject = [PSCustomObject]@{
                    Model   = $Model
                    Average = $ModelAvg
                }
                $RecentAverages += $AvgObject
                if ($i -gt 0) {
                    Write-Output ($Recent5 | Select-Object Date, Model, Time | Format-Table -AutoSize -HideTableHeaders)
                }
                else {
                    Write-Output ($Recent5 | Select-Object Date, Model, Time | Format-Table -AutoSize)
                }
                Write-Color ' Average (last 5): ', "$($ModelAvg)", "`n`n" -C Gray, Yellow, Gray

                $i++
            }
            Write-Output ($RecentAverages | Sort-Object Model, Average | Format-Table -AutoSize)
            Return
        }
        elseif ($Average) {
            Return $Avg
        }
        else {
            Return $Results
        }
    }
}

function Get-Logs {
    <#
 
    .SYNOPSIS
    Gets the last X lines from a log file
 
    .DESCRIPTION
    Gets the last X lines from a log file
 
    .PARAMETER Log
    The log file to get the lines from
 
    .PARAMETER Amount
    The amount of lines to get
 
    .PARAMETER LogsPath
    The path to the logs folder
 
    .EXAMPLE
    Get-Logs -Log Ping -Amount 3
 
    .EXAMPLE
    Get-Logs -Log Plex -Amount 3
 
    .EXAMPLE
    Get-Logs -Log Plex -Amount 3 -LogsPath "C:\Stuff\Logs"
 
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]$Log,
        [Parameter(Mandatory = $false)][switch]$All = $false,
        [Parameter(Mandatory = $false)]$Amount = 3
    )

    $Log = $Log.ToLower()
    $LogPath = "$env:logs\$Log.log"

    if ($All) {
        $Lines = (Get-Content "$LogPath") | Where-Object { $_ -notlike '' }
    }
    else {
        $Lines = (Get-Content "$LogPath") | Where-Object { $_ -notlike '' } | Select-Object -Last $Amount
    }
    $Output = @()

    foreach ($Line in $Lines) {
        $Time = ((($Line -split '\]').TrimStart('\['))[0]).Replace('-', '/').Replace('.', ':')

        $Instance = [PSCustomObject]@{
            Context = (($Line -split '\[User: ')[-1] -split '\]')[0]
            PID     = (($Line -split '\[PID: ')[1] -split '\]')[0]
            Time    = [DateTime]::ParseExact($Time, 'dd/MM HH:mm:ss', ([System.Globalization.CultureInfo]::InvariantCulture))
            Running = $false
            Message = (($Line -split '\[User: ')[-1] -split '\]')[1].TrimStart(' ')
        }
        if ($Log -like 'deluged') {
            switch (($Line -split ('Deluged switch: '))[-1]) {
                'on' {
                    $Instance.Running = $true 
                }
                'off' {
                    $Instance.Running = $false 
                }
            }
        }
        elseif ((Get-Process -Id $Instance.PID -ErrorAction SilentlyContinue).Count -gt 0) {
            $Instance.Running = $true
        }
        $Output += $Instance
    }
    Return $Output
}

function New-awsEvent {
    <#
    .SYNOPSIS
    Creates a new event in the aws log
 
    .DESCRIPTION
    Creates a new event in the aws log
 
    .PARAMETER evtID
    The event ID. Can be shortened to "id".
 
    .PARAMETER message
    The message to log
 
    .PARAMETER var1
    The first variable to log
 
    .PARAMETER var2
    The second variable to log
 
    .PARAMETER source
    The source of the event (can be either "Script" (default), "Plex" or "Torrent"). Can be shortened to "s".
 
    .PARAMETER type
    The type of event to create (can be either "Information" (default), "Warning" or "Error"). Can be shortened to "t".
 
    .EXAMPLE
    New-awsEvent 100 "This is a test"
 
    .EXAMPLE
    New-awsEvent 102 "This is a test" "This fills out a variable" "This fills out the second variable" -s "Script" -t "Information"
 
    .NOTES
 
 
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false, Position = 0)][Alias('id')][int]$evtID = 100,
        [Parameter(Mandatory = $false, Position = 1, ValueFromPipeline)][Alias('m')][string]$Message,
        [Parameter(Mandatory = $false, ValueFromPipeline)][Alias('v1')][string]$var1,
        [Parameter(Mandatory = $false, ValueFromPipeline)][Alias('v2')][string]$var2,
        [Parameter(Mandatory = $false, Position = 2)][Alias('s')][string]$Source,
        [Parameter(Mandatory = $false, Position = 3)][Alias('t')][ValidateSet('Information', 'Info', 'i', 'Warning', 'w', 'Error', 'e')][string]$Type
    )
    Begin {

        if (!$Source) {
            switch ($evtID) {
                100 {
                    $source = 'Script' ; $id = New-Object System.Diagnostics.EventInstance($evtID, 1) ; $EventType = 'Information' 
                }
                101 {
                    $source = 'Script' ; $id = New-Object System.Diagnostics.EventInstance($evtID, 1) ; $EventType = 'Information' 
                }
                102 {
                    $source = 'Script' ; $id = New-Object System.Diagnostics.EventInstance($evtID, 1, 1) ; $EventType = 'Error' 
                }
                200 {
                    $source = 'Plex' ; $id = New-Object System.Diagnostics.EventInstance($evtID, 1) ; $EventType = 'Information' 
                }
                201 {
                    $source = 'Plex' ; $id = New-Object System.Diagnostics.EventInstance($evtID, 1) ; $EventType = 'Information' 
                }
                202 {
                    $source = 'Plex' ; $id = New-Object System.Diagnostics.EventInstance($evtID, 1, 1) ; $EventType = 'Error' 
                }
                300 {
                    $source = 'Torrent' ; $id = New-Object System.Diagnostics.EventInstance($evtID, 1) ; $EventType = 'Information' 
                }
                400 {
                    $source = 'LLM' ; $id = New-Object System.Diagnostics.EventInstance($evtID, 1) ; $EventType = 'Information' 
                }
                401 {
                    $source = 'LLM' ; $id = New-Object System.Diagnostics.EventInstance($evtID, 1) ; $EventType = 'Information' 
                }
                402 {
                    $source = 'LLM' ; $id = New-Object System.Diagnostics.EventInstance($evtID, 1) ; $EventType = 'Information' 
                }
                default {
                    $source = 'Script' ; $id = New-Object System.Diagnostics.EventInstance($evtID, 1) ; $EventType = 'Information' 
                }
            }
        }
        if ($Type) {
            switch ($type.ToLower()) {
                'information' {
                    $id = New-Object System.Diagnostics.EventInstance($evtID, 1) ; $EventType = 'Information' 
                }
                'info' {
                    $id = New-Object System.Diagnostics.EventInstance($evtID, 1) ; $EventType = 'Information' 
                }
                'i' {
                    $id = New-Object System.Diagnostics.EventInstance($evtID, 1) ; $EventType = 'Information' 
                }
                'warning' {
                    $id = New-Object System.Diagnostics.EventInstance($evtID, 1, 2) ; $EventType = 'Warning' 
                }
                'w' {
                    $id = New-Object System.Diagnostics.EventInstance($evtID, 1, 2) ; $EventType = 'Warning' 
                }
                'error' {
                    $id = New-Object System.Diagnostics.EventInstance($evtID, 1, 1) ; $EventType = 'Error' 
                }
                'e' {
                    $id = New-Object System.Diagnostics.EventInstance($evtID, 1, 1) ; $EventType = 'Error' 
                }
            }
        }
    }
    Process {
        $evtObject = New-Object System.Diagnostics.EventLog
        $evtObject.Log = 'aws'
        $evtObject.Source = $source
        try {
            $evtObject.WriteEvent($id, @($message, $var1, $var2))
        }
        catch {
            throw $_.Exception
        }
        finally {
            $evtObject.Dispose()
        }
    }
    End {
        $Return = [PSCustomObject]@{
            Log     = $evtObject.Log
            Type    = $EventType
            Source  = $evtObject.Source
            EventID = $evtID
            Message = "$($message)$($var1)$($var2)"
        }
        Return ($Return | Format-Table -AutoSize)
    }
}

function Test-Ping {
    <#
 
    .SYNOPSIS
    Tests if the internet is reachable
 
    .DESCRIPTION
    Tests if the internet is reachable
 
    .EXAMPLE
    Test-Ping
 
    .PARAMETER Limit
    The amount of times to test the internet before returning false
 
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    Param(
        [Parameter(Mandatory = $false)]
        [int] $Limit = 3
    )
    $Repeat = $true
    $i = 0

    while ($Repeat) {
        $Ping = Test-Connection 8.8.8.8

        if (($Ping | Where-Object { $_.Status -eq 'Success' }).count -eq 0) {

            if ($i -ge $Limit) {
                Return $false
            }
            else {
                $i++
                Start-Sleep -Seconds 30
            }
        }
        else {
            $Repeat = $false
        }
    }
    Return $true
}

function Start-SystemPS {
    <#
 
    .SYNOPSIS
    Starts a new PowerShell session as SYSTEM
 
    .DESCRIPTION
    Starts a new PowerShell session as SYSTEM
 
    .EXAMPLE
    Start-SystemPS
 
    #>

    [CmdletBinding()]
    param ()

    C:\Stuff\PSTools\PsExec64.exe -s -i 1 pwsh.exe
}

function Install-CustomModule {
    <#
 
    .SYNOPSIS
    Installs a module from the PowerShell Gallery, or imports it if it is already installed.
 
    .DESCRIPTION
    Installs a module from the PowerShell Gallery, or imports it if it is already installed.
 
    .EXAMPLE
    Install-CustomModule -Module 'Pester'
 
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $Module
    )
    process {
        # Check if the module is already installed on the machine
        if ( -not ( Get-Module $Module -ListAvailable ) ) {
            # Install the module as user scripts
            Install-Module $Module -Scope CurrentUser -Force
            Import-Module $Module
        }
        # The module was already installed
        if ( ( Get-Module $Module -ListAvailable ) ) {
            # Check if the module is already imported
            if ( -not ( Get-Module $Module ) ) {
                Import-Module $Module
            }
        }
    }
}

function Restart-AsAdmin {
    <#
 
    .SYNOPSIS
    Restarts the script as an administrator
 
    .DESCRIPTION
    Restarts the script as an administrator
 
    .EXAMPLE
    Restart-AsAdmin
 
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param (
    )
    process {
        $Path = '"' + $PSCommandPath + '"'
        $Script:User = [Security.Principal.WindowsIdentity]::GetCurrent()
        $Script:UserObject = (New-Object Security.Principal.WindowsPrincipal $User)
        if (($UserObject.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) -eq $false) -or ($host.Name -notlike 'ConsoleHost')) {
            $ArgList = @(
                "-file $Path",
                '-NoExit'
            )
            Start-Process pwsh.exe -Verb runas -ArgumentList $ArgList
            Exit
        }
    }
}

function Send-Reannounce {
    [CmdletBinding()]
    [OutputType([String])]
    param (
        [String]$Port = '8087'
    )
    begin {
    }
    process {
        $URI = 'http://192.168.0.127:' + $Port + '/'
        $data = 'username=aws&password=asdf1234'

        try {
            Invoke-RestMethod -Uri ($URI + 'api/v2/auth/login') -Headers @{'Referer' = $URI } -Method POST -Body $data -SessionVariable QBTSession -TimeoutSec 60
        }
        catch {
            Return 'Failed to login to qBittorrent'
        }

        try {
            Invoke-RestMethod -Uri ($URI + 'api/v2/torrents/reannounce') -WebSession $QBTSession -Method POST -Body 'hashes=all&value=true' -TimeoutSec 60
        }
        catch {
            Return 'Failed to send reannounce'
        }

    }
    end {
    }
}

function Limit-LogSize {
    <#
 
    .SYNOPSIS
    Deletes the oldest 75% of a log if it exceeds a specified size.
    .DESCRIPTION
    Deletes the oldest 75% of a log if it exceeds a specified size.
    .PARAMETER Size
    The maximum acceptable size of the log file in KB.
    .EXAMPLE
    Limit-LogSize -Size 1024
    .OUTPUTS
    The output of the function is a string containing the name of the log file and the size before and after the function was run.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]$Size = 2048
    )

    process {
        $Logs = Get-ChildItem $env:logs
        $Output = ''

        foreach ($Log in $Logs) {
            $LogSize = [math]::Round(((Get-ChildItem $Log.fullname | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1KB), 2)
            $LogSizeMB = [math]::Round(((Get-ChildItem $Log.fullname | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1MB), 2)

            if ($LogSize -gt $Size) {
                $Content = $Log | Get-Content
                $LinesToRemove = ($Content.count * 0.75)
                $Content = $Content | Select-Object -Skip $LinesToRemove
                $Content | Out-File $Log.fullname -Force
                if ($LogSizeMB -gt 1) {
                    $Output += "`n Size of $($Log.name) = $LogSizeMB MB, oldest 75% of log deleted. `n New size: $([math]::Round(((Get-ChildItem $Log.fullname | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1KB), 2)) KB`n"
                }
                else {
                    $Output += "`n Size of $($Log.name) = $LogSize KB, oldest 75% of log deleted. `n New size:$([math]::Round(((Get-ChildItem $Log.fullname | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1KB), 2)) KB`n"
                }
            }
            else {
                $Output += "`n Size of $($Log.name) = $LogSize KB, no changes made.`n"
            }
        }
        Return $Output
    }
}

function Convert-Currency {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)][string]$targetCurrency,
        [Parameter(Mandatory = $true, Position = 1)][string]$amount,
        [Parameter(Mandatory = $true, Position = 2)][string]$currency
    )

    $resultAs = $targetCurrency.ToUpper()
    $currency = $currency.ToUpper()

    $headers = @{
        'apikey' = 'b4xTe0eLZKy8lSqkgjczdWboc4d2eIqo'
    }

    [string]$apiCall = "https://api.apilayer.com/exchangerates_data/convert?to=$resultAs&from=$currency&amount=$amount"

    $exhangeRates = Invoke-RestMethod -Uri $apiCall -Headers $headers -Method Get

    Return "$([math]::Round($exhangeRates.result, 2)) $resultAs"

}

function Use-ChatGPT {
    <#
 
    .SYNOPSIS
    Sends a message to the OpenAI API and returns the chatbot's response.
    .DESCRIPTION
    Sends a message to the OpenAI API and returns the chatbot's response.
    .PARAMETER Message
    The message to send to the chatbot. The message must be a string. The message can be piped to the function, or used as an unnamed parameter.
    .PARAMETER Model
    The model to use for the chatbot. The default value is 3, which uses the "gpt-3.5-turbo" model. The value 4 uses the "gpt-4" model, which is $0.06/1k tokens (30 times the price of the "gpt-3.5-turbo" model).
    .EXAMPLE
    Use-ChatGPT -Message "Hello, how are you today?"
    .OUTPUTS
    The output of the function is the chatbot's response to the message sent to the API.
 
    #>

    [CmdletBinding()]
    [OutputType([String])]
    param(
        [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline )][String]$Message,
        [Parameter(Mandatory = $false, Position = 2)][int]$Tokens = 0,
        [Parameter(Mandatory = $false, Position = 3)][int]$Model = 3
    )

    Begin {
    }

    Process {
        # The function uses a switch statement to assign a value to the $model variable based on the value of $Model. If $Model is 3, then $model is assigned the value "gpt-3.5-turbo". If $Model is 4, then $model is assigned the value "gpt-4".
        switch ($Model) {
            3 {
                [string]$model = 'gpt-3.5-turbo' 
            }
            4 {
                [string]$model = 'gpt-4' 
            }
        }


        # Next, the function sets up an API request to the OpenAI API endpoint for chat completions. It creates a hash table named $messages with two key-value pairs: role="user" and content=$Message. It then creates another hash table named $json with five key-value pairs: model=$model, messages=@($messages), max_tokens=50, temperature=0.5, and n=1. This hash table will be used as the request body for the API call.

        $url = 'https://api.openai.com/v1/chat/completions'
        $messages = @{
            role    = 'user'
            content = $Message
        }
        $json = @{
            model       = $model
            messages    = @($messages)
            temperature = 1.7
            n           = 1
        }

        switch ($Tokens) {
            0 {
                Break 
            }
            default {
                $json.max_tokens = $Tokens 
            }
        }

        # The function then sets up the headers for the API request, including the Content-Type and Authorization headers.
        $headers = @{
            'Content-Type'        = 'application/json'
            'Authorization'       = 'Bearer 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'
            'OpenAI-Organization' = 'org-rFpwivMhpH8OMr4XcSj4mZmW'
        }

        # It sends the API request using Invoke-RestMethod cmdlet from PowerShell and gets the response.
        $response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body ($json | ConvertTo-Json -Depth 10)
    }

    End {
        switch ($Model) {
            'gpt-3.5-turbo' {
                [double]$CostUSD = (($($response.usage.total_tokens) / 1000) * 0.002) 
            }
            'gpt-4' {
                [double]$CostUSD = (($($response.usage.prompt_tokens) / 1000) * 0.03) + (($($response.usage.completion_tokens) / 1000) * 0.06) 
            }
        }

        [Double]$CostDKK = [math]::Round(($CostUSD * 6.75), 4)

        # Finally, the function returns the generated text from the response by accessing the content property of the message property of the first choice of the response.choices array.
        Write-Output "`nTokens used: $($response.usage.total_tokens) (P: $($response.usage.prompt_tokens) | C: $($response.usage.completion_tokens))`nPrice: $CostDKK DKK`n"
        Write-Output ($response.choices.message.content)
        Return "`n"
    }
}

function Use-gpt4free {
    [Alias('ugf')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline )][String]$Message
    )

    Begin {

    }

    Process {
        $py = Get-Content 'C:\Users\aws\Repos\gpt4free\test.py'
        $newpy = $py.Replace('<CONTENT>', $Message)
        $newpy | Out-File 'C:\Users\aws\Repos\gpt4free\test2.py' -Force
        python 'C:\Users\aws\Repos\gpt4free\test2.py'
    }

    End {

    }
}

function Set-WindowStyle {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $false, Position = 1)]
        [ValidateSet('FORCEMINIMIZE', 'HIDE', 'MAXIMIZE', 'MINIMIZE', 'RESTORE',
            'SHOW', 'SHOWDEFAULT', 'SHOWMAXIMIZED', 'SHOWMINIMIZED',
            'SHOWMINNOACTIVE', 'SHOWNA', 'SHOWNOACTIVATE', 'SHOWNORMAL')]$Style,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 2)]$MainWindowHandle
    )
    $WindowStates = @{
        FORCEMINIMIZE = 11; HIDE = 0
        MAXIMIZE = 3; MINIMIZE = 6
        RESTORE = 9; SHOW = 5
        SHOWDEFAULT = 10; SHOWMAXIMIZED = 3
        SHOWMINIMIZED = 2; SHOWMINNOACTIVE = 7
        SHOWNA = 8; SHOWNOACTIVATE = 4
        SHOWNORMAL = 1
    }
    Write-Verbose ('Set Window Style {1} on handle {0}' -f $MainWindowHandle, $($WindowStates[$style]))

    $Win32ShowWindowAsync = Add-Type -MemberDefinition @'
[DllImport("user32.dll")]
public static extern bool ShowWindowAsync(int hWnd, int nCmdShow);
'@
 -Name 'Win32ShowWindowAsync' -Namespace Win32Functions -PassThru

    $Win32ShowWindowAsync::ShowWindowAsync($MainWindowHandle, $WindowStates[$Style]) | Out-Null
}

function Invoke-FFmpeg {
    <#
    .SYNOPSIS
    Uses FFmpeg to convert a video file to an MP4 file.
 
    .DESCRIPTION
    Uses FFmpeg to convert a video file to an MP4 file.
 
    .PARAMETER Infile
    The file to convert. This must be a string or FileInfo object.
 
    .PARAMETER Replace
    If this switch is used, the original file will be replaced with the converted file.
 
    .EXAMPLE
    Invoke-FFmpeg -Infile "C:\Users\Public\Videos\Sample Videos\Wildlife.wmv"
 
    .EXAMPLE
    Invoke-FFmpeg -Infile "C:\Users\Public\Videos\Sample Videos\Wildlife.wmv" -Replace
 
    .EXAMPLE
    Get-ChildItem -Path "C:\Users\Public\Videos\Sample Videos" -Filter "*.wmv" | Invoke-FFmpeg
 
    .OUTPUTS
    The output of the function is the file that was converted.
    #>


    [Alias('ffmp')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]$Infile,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false)][Alias('r')][switch]$Replace = $false
    )
    Begin {
        $i = 0
    }
    Process {

        if (($Infile.GetType().Name) -like 'FileInfo') {
            $in = $Infile.FullName
            $out = ((($Infile.PSParentPath).Replace('Microsoft.PowerShell.Core\FileSystem::', '')) + '\' + "$($Infile.BaseName)" + '.mp4')
        }
        elseif (($Infile.GetType().Name) -like 'String') {
            $in = $Infile
            $out = $Infile + '.mp4'
        }
        else {
            Return "`nInput must be a string or FileInfo object.`n"
        }

        $cmd = "ffmpeg -i ""$in"" -c:v hevc_nvenc -preset fast ""$out"""
        Invoke-Expression $cmd

        if ($Replace) {
            Remove-Item $in
        }

        $i++
        Return "`n`nOutput: $out`n`nFiles processed: $i`n`n"
    }
    End {
    }
}

function Connect-AI {
    <#
 
    .SYNOPSIS
    Connects to the AI.
 
    .DESCRIPTION
    Connects to the AI.
 
    .PARAMETER AI
    The AI to connect to. This must be a string.
 
    .EXAMPLE
    Connect-AI -AI "bing"
 
    .EXAMPLE
    Connect-AI -AI "chatgpt"
 
    .OUTPUTS
    The output of the function is the chat session.
 
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 1)]$AI = 'bing'
    )

    switch ($AI.ToLower()) {
        bing {
            $Settings = 'settingsbing.js' 
        }
        chatgpt {
            $Settings = 'settingschatgpt.js' 
        }
        default {
            Return "`nAI not found.`n" 
        }
    }

    Copy-Item -Path "$env:userprofile\Repos\aws\node-chatgpt-api\$settings" -Destination "$env:userprofile\Repos\aws\node-chatgpt-api\settings.js" -Force

    Set-Location "$env:userprofile\Repos\aws\node-chatgpt-api\"

    npm run cli

    Return
}

function Set-WindowStyle {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $false, Position = 1)]
        [ValidateSet('FORCEMINIMIZE', 'HIDE', 'MAXIMIZE', 'MINIMIZE', 'RESTORE',
            'SHOW', 'SHOWDEFAULT', 'SHOWMAXIMIZED', 'SHOWMINIMIZED',
            'SHOWMINNOACTIVE', 'SHOWNA', 'SHOWNOACTIVATE', 'SHOWNORMAL')]$Style,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 2)]$MainWindowHandle
    )
    $WindowStates = @{
        FORCEMINIMIZE = 11; HIDE = 0
        MAXIMIZE = 3; MINIMIZE = 6
        RESTORE = 9; SHOW = 5
        SHOWDEFAULT = 10; SHOWMAXIMIZED = 3
        SHOWMINIMIZED = 2; SHOWMINNOACTIVE = 7
        SHOWNA = 8; SHOWNOACTIVATE = 4
        SHOWNORMAL = 1
    }
    Write-Verbose ('Set Window Style {1} on handle {0}' -f $MainWindowHandle, $($WindowStates[$style]))

    $Win32ShowWindowAsync = Add-Type -MemberDefinition @'
[DllImport("user32.dll")]
public static extern bool ShowWindowAsync(int hWnd, int nCmdShow);
'@
 -Name 'Win32ShowWindowAsync' -Namespace Win32Functions -PassThru

    $Win32ShowWindowAsync::ShowWindowAsync($MainWindowHandle, $WindowStates[$Style]) | Out-Null
}

function Start-CiscoTray {
    [CmdletBinding()]
    param(
    )
    Begin {
        $CSV_Array = @'
"Question","Answer"
"Match the type of ASA ACLs to the description. (Not all options are used.)",""
"Which statement describes a difference between the Cisco ASA IOS CLI feature and the router IOS CLI feature?","To use a show command in a general configuration mode, ASA can use the command directly whereas a router will need to enter the do command before issuing the show command."
"Refer to the exhibit. A network administrator is configuring AAA implementation on an ASA device. What does the option link3 indicate?","the interface name"
"What provides both secure segmentation and threat defense in a Secure Data Center solution?","Adaptive Security Appliance"
"What are the three core components of the Cisco Secure Data Center solution? (Choose three.)","secure segmentation
visibility
threat defense"
"What are three characteristics of ASA transparent mode? (Choose three.)","This mode does not support VPNs, QoS, or DHCP Relay.
This mode is referred to as a “bump in the wire.”
In this mode the ASA is invisible to an attacker."
"What is needed to allow specific traffic that is sourced on the outside network of an ASA firewall to reach an internal network?","ACL"
"What will be the result of failed login attempts if the following command is entered into a router?","All login attempts will be blocked for 150 seconds if there are 4 failed attempts within 90 seconds."
"Which two tasks are associated with router hardening? (Choose two.)","disabling unused ports and interfaces
securing administrative access"
"Which threat protection capability is provided by Cisco ESA?","spam protection"
"What are two security measures used to protect endpoints in the borderless network? (Choose two.)","denylisting
DLP"
"Which three types of traffic are allowed when the authentication port-control auto command has been issued and the client has not yet been authenticated? (Choose three.)","CDP
STP
EAPOL"
"Which statement describes a characteristic of the IKE protocol?","It uses UDP port 500 to exchange IKE information between the security gateways."
"Which action do IPsec peers take during the IKE Phase 2 exchange?","negotiation of IPsec policy"
"What are two hashing algorithms used with IPsec AH to guarantee authenticity? (Choose two.)","SHA
MD5"
"Which command raises the privilege level of the ping command to 7?","privilege exec level 7 ping"
"What is a characteristic of a role-based CLI view of router configuration?","A single CLI view can be shared within multiple superviews."
"What is a limitation to using OOB management on a large enterprise network?","All devices appear to be attached to a single management network."
"Refer to the exhibit. A corporate network is using NTP to synchronize the time across devices. What can be determined from the displayed output?","Router03 is a stratum 2 device that can provide NTP service to other devices in the network."
"Refer to the exhibit. Which two conclusions can be drawn from the syslog message that was generated by the router? (Choose two.)","This message indicates that service timestamps have been configured.
This message is a level 5 notification message."
"Which two types of hackers are typically classified as grey hat hackers? (Choose two.)","hacktivists
vulnerability brokers"
"When describing malware, what is a difference between a virus and a worm?","A virus replicates itself by attaching to another file, whereas a worm can replicate itself independently."
"Which type of packet is unable to be filtered by an outbound ACL?","router-generated packet"
"Consider the access list command applied outbound on a router serial interface.","No traffic will be allowed outbound on the serial interface."
"Which command is used to activate an IPv6 ACL named ENG_ACL on an interface so that the router filters traffic prior to accessing the routing table?","ipv6 traffic-filter ENG_ACL in"
"What technology has a function of using trusted third-party protocols to issue credentials that are accepted as an authoritative identity?","PKI certificates"
"What are two methods to maintain certificate revocation status? (Choose two.)","OCSP
CRL"
"Which protocol is an IETF standard that defines the PKI digital certificate format?","X.509"
"A network administrator is configuring DAI on a switch. Which command should be used on the uplink interface that connects to a router?","ip arp inspection trust"
"What is the best way to prevent a VLAN hopping attack?","Disable trunk negotiation for trunk ports and statically set nontrunk ports as access ports."
"What would be the primary reason an attacker would launch a MAC address overflow attack?","so that the attacker can see frames that are destined for other hosts"
"What is the main difference between the implementation of IDS and IPS devices?","An IDS would allow malicious traffic to pass before it is addressed, whereas an IPS stops it immediately."
"Which attack is defined as an attempt to exploit software vulnerabilities that are unknown or undisclosed by the vendor?","zero-day"
"Match the network monitoring technology with the description.",""
"What are the three signature levels provided by Snort IPS on the 4000 Series ISR? (Choose three.)","security
connectivity
balanced"
"What are three attributes of IPS signatures? (Choose three.)","action
trigger
type"
"Match each IPS signature trigger category with the description.","pattern-based detection:
anomaly-based detection:
honey pot-based detection:"
"Which two features are included by both TACACS+ and RADIUS protocols? (Choose two.)","password encryption
utilization of transport layer protocols"
"What function is provided by the RADIUS protocol?","RADIUS provides separate ports for authorization and accounting."
"What are three characteristics of the RADIUS protocol? (Choose three.)","uses UDP ports for authentication and accounting
supports 802.1X and SIP
is an open RFC standard AAA protocol"
"Which zone-based policy firewall zone is system-defined and applies to traffic destined for the router or originating from the router?","self zone"
"What are two benefits of using a ZPF rather than a Classic Firewall? (Choose two.)","The ZPF is not dependent on ACLs.
ZPF policies are easy to read and troubleshoot."
"Place the steps for configuring zone-based policy (ZPF) firewalls in order from first to last. (Not all options are used.)",""
"How does a firewall handle traffic when it is originating from the private network and traveling to the DMZ network?","The traffic is usually permitted with little or no restrictions."
"Which two protocols generate connection information within a state table and are supported for stateful filtering? (Choose two.)","UDP
TCP"
"Which type of firewall is supported by most routers and is the easiest to implement?","stateless firewall"
"What network testing tool would an administrator use to assess and validate system configurations against security policies and compliance standards?","Tripwire"
"What type of network security test can detect and report changes made to network systems?","integrity checking"
"What network security testing tool has the ability to provide details on the source of suspicious network activity?","SIEM"
"How do modern cryptographers defend against brute-force attacks?","Use a keyspace large enough that it takes too much money and too much time to conduct a successful attack."
"How does a Caesar cipher work on a message?","Letters of the message are replaced by another letter that is a set number of places away in the alphabet."
"What is the main factor that ensures the security of encryption of modern algorithms?","secrecy of the keys"
"What is the next step in the establishment of an IPsec VPN after IKE Phase 1 is complete?","negotiation of the IPsec SA policy"
"Refer to the exhibit. What algorithm will be used for providing confidentiality?","AES"
"After issuing a show run command, an analyst notices the following command:","It establishes the set of encryption and hashing algorithms used to secure the data sent through an IPsec tunnel."
"Which algorithm can ensure data integrity?","MD5"
"A company implements a security policy that ensures that a file sent from the headquarters office to the branch office can only be opened with a predetermined code. This code is changed every day. Which two algorithms can be used to achieve this task? (Choose two.)","3DES
AES"
"A network technician has been asked to design a virtual private network between two branch routers. Which type of cryptographic key should be used in this scenario?","symmetric key"
"Which two options can limit the information discovered from port scanning? (Choose two.)","intrusion prevention system
firewall"
"An administrator discovers that a user is accessing a newly established website that may be detrimental to company security. What action should the administrator take first in terms of the security policy?","Revise the AUP immediately and get all users to sign the updated AUP."
"If AAA is already enabled, which three CLI steps are required to configure a router with a specific view? (Choose three.)","Create a view using the parser view view-name command.
Assign a secret password to the view.
Assign commands to the view."
"Refer to the exhibit. A network administrator configures a named ACL on the router. Why is there no output displayed when the show command is issued?","The ACL name is case sensitive."
"ACLs are used primarily to filter traffic. What are two additional uses of ACLs? (Choose two.):","specifying internal hosts for NAT
identifying traffic for QoS"
"What two features are added in SNMPv3 to address the weaknesses of previous versions of SNMP? (Choose two.)","authentication
encryption"
"What network testing tool is used for password auditing and recovery?","L0phtcrack"
"Which type of firewall makes use of a server to connect to destination devices on behalf of clients?","proxy firewall"
"Refer to the exhibit. What will be displayed in the output of the show running-config object command after the exhibited configuration commands are entered on an ASA 5506-X?","range 192.168.1.10 192.168.1.20"
"Refer to the exhibit. According to the command output, which three statements are true about the DHCP options entered on the ASA? (Choose three.)","The dhcpd address [ start-of-pool ]-[ end-of-pool ] inside command was issued to enable the DHCP server.
The dhcpd enable inside command was issued to enable the DHCP server.
The dhcpd auto-config outside command was issued to enable the DHCP client."
"Which two statements describe the characteristics of symmetric algorithms? (Choose two.)","They are commonly used with VPN traffic.
They are referred to as a pre-shared key or secret key."
"A web server administrator is configuring access settings to require users to authenticate first before accessing certain web pages. Which requirement of information security is addressed through the configuration?","confidentiality"
"The use of 3DES within the IPsec framework is an example of which of the five IPsec building blocks?","confidentiality"
"What function is provided by Snort as part of the Security Onion?","to generate network intrusion alerts by the use of rules and signatures"
"What are two drawbacks to using HIPS? (Choose two.)","With HIPS, the network administrator must verify support for all the different operating systems used in the network.
HIPS has difficulty constructing an accurate network picture or coordinating events that occur across the entire network."
"In an AAA-enabled network, a user issues the configure terminal command from the privileged executive mode of operation. What AAA function is at work if this command is rejected?","authorization"
"A company has a file server that shares a folder named Public. The network security policy specifies that the Public folder is assigned Read-Only rights to anyone who can log into the server while the Edit rights are assigned only to the network admin group. Which component is addressed in the AAA network service framework?","authorization"
"What is a characteristic of a DMZ zone?","Traffic originating from the outside network going to the DMZ network is selectively permitted."
"Which measure can a security analyst take to perform effective security monitoring against network traffic encrypted by SSL technology?","Deploy a Cisco SSL Appliance."
"Refer to the exhibit. Port security has been configured on the Fa 0/12 interface of switch S1. What action will occur when PC1 is attached to switch S1 with the applied configuration?","Frames from PC1 will cause the interface to shut down immediately, and a log entry will be made."
"What security countermeasure is effective for preventing CAM table overflow attacks?","port security"
"What are two examples of DoS attacks? (Choose two.)","ping of death buffer overflow"
"Which method is used to identify interesting traffic needed to create an IKE phase 1 tunnel?","a permit access list entry"
"When the CLI is used to configure an ISR for a site-to-site VPN connection, which two items must be specified to enable a crypto map policy? (Choose two.)","the peer
a valid access list"
"How does a firewall handle traffic when it is originating from the public network and traveling to the DMZ network?","Traffic that is originating from the public network is inspected and selectively permitted when traveling to the DMZ network."
"A client connects to a Web server. Which component of this HTTP connection is not examined by a stateful firewall?","the actual contents of the HTTP connection"
"Which network monitoring technology uses VLANs to monitor traffic on remote switches?","RSPAN"
"Which rule action will cause Snort IPS to block and log a packet?","drop"
"What is typically used to create a security trap in the data center facility?","IDs, biometrics, and two access doors"
"A company is concerned with leaked and stolen corporate data on hard copies. Which data loss mitigation technique could help with this situation?","shredding"
"Upon completion of a network security course, a student decides to pursue a career in cryptanalysis. What job would the student be doing as a cryptanalyst?","cracking code without access to the shared secret key"
"What command is used on a switch to set the port access entity type so the interface acts only as an authenticator and will not respond to any messages meant for a supplicant?","dot1x pae authenticator"
"What are two disadvantages of using an IDS? (Choose two.)","The IDS does not stop malicious traffic.
The IDS requires other devices to respond to attacks."
"Refer to the exhibit. The ip verify source command is applied on untrusted interfaces. Which type of attack is mitigated by using this configuration?","MAC and IP address spoofing"
"What ports can receive forwarded traffic from an isolated port that is part of a PVLAN?","only promiscuous ports"
"A user complains about being locked out of a device after too many unsuccessful AAA login attempts. What could be used by the network administrator to provide a secure authentication access method without locking a user out of a device?","Use the login delay command for authentication attempts."
"What are two drawbacks in assigning user privilege levels on a Cisco router? (Choose two.)","Assigning a command with multiple keywords allows access to all commands using those keywords.
Commands from a lower level are always executable at a higher level."
"Refer to the exhibit. Which conclusion can be made from the show crypto map command output that is shown on R1?","The crypto map has not yet been applied to an interface."
"What are two reasons to enable OSPF routing protocol authentication on a network? (Choose two.)","to prevent data traffic from being redirected and then discarded
to prevent redirection of data traffic to an insecure link"
"Which three functions are provided by the syslog logging service? (Choose three.)","gathering logging information
specifying where captured information is stored
distinguishing between information to be captured and information to be ignored"
"What two ICMPv6 message types must be permitted through IPv6 access control lists to allow resolution of Layer 3 addresses to Layer 2 MAC addresses? (Choose two.)",""
"Which three services are provided through digital signatures? (Choose three.)","authenticity
nonrepudiation
integrity"
"A technician is to document the current configurations of all network devices in a college, including those in off-site buildings. Which protocol would be best to use to securely access the network devices?","SSH"
"An administrator is trying to develop a BYOD security policy for employees that are bringing a wide range of devices to connect to the company network. Which three objectives must the BYOD security policy address? (Choose three.)","Rights and activities permitted on the corporate network must be defined.
Safeguards must be put in place for any personal device being compromised.
The level of access of employees when connecting to the corporate network must be defined."
"What is the function of the pass action on a Cisco IOS Zone-Based Policy Firewall?","forwarding traffic from one zone to another"
"Refer to the exhibit. Based on the security levels of the interfaces on ASA1, what traffic will be allowed on the interfaces?","Traffic from the LAN and DMZ can access the Internet."
"What network testing tool can be used to identify network layer protocols running on a host?","Nmap"
"In the implementation of security on multiple devices, how do ASA ACLs differ from Cisco IOS ACLs?","Cisco IOS ACLs are configured with a wildcard mask and Cisco ASA ACLs are configured with a subnet mask."
"Which statement describes an important characteristic of a site-to-site VPN?","It must be statically set up."
"Which two options are security best practices that help mitigate BYOD risks? (Choose two.)","Keep the device OS and software updated.
Only turn on Wi-Fi when using the wireless network."
"Refer to the exhibit. A network administrator configures AAA authentication on R1. Which statement describes the effect of the keyword single-connection in the configuration?","The authentication performance is enhanced by keeping the connection to the TACACS+ server open."
"A recently created ACL is not working as expected. The admin determined that the ACL had been applied inbound on the interface and that was the incorrect direction. How should the admin fix this issue?","Delete the original ACL and create a new ACL, applying it outbound on the interface."
"What characteristic of the Snort term-based subscriptions is true for both the community and the subscriber rule sets?","Both offer threat protection against security threats."
"A security analyst is configuring Snort IPS. The analyst has just downloaded and installed the Snort OVA file. What is the next step?","Configure Virtual Port Group interfaces."
"The security policy in a company specifies that employee workstations can initiate HTTP and HTTPS connections to outside websites and the return traffic is allowed. However, connections initiated from outside hosts are not allowed. Which parameter can be used in extended ACLs to meet this requirement?","established"
"A researcher is comparing the differences between a stateless firewall and a proxy firewall. Which two additional layers of the OSI model are inspected by a proxy firewall? (Choose two.)","Layer 5
Layer 7"
"Refer to the exhibit. A network administrator is configuring a VPN between routers R1 and R2. Which commands would correctly configure a pre-shared key for the two routers?",""
"Refer to the exhibit. Which statement is true about the effect of this Cisco IOS zone-based policy firewall configuration?","The firewall will automatically allow HTTP, HTTPS, and FTP traffic from g0/0 to s0/0/0 and will track the connections. Tracking the connection allows only return traffic to be permitted through the firewall in the opposite direction."
"Which privilege level has the most access to the Cisco IOS?","level 15"
"Refer to the exhibit. A network administrator has configured NAT on an ASA device. What type of NAT is used?","inside NAT"
"A network analyst is configuring a site-to-site IPsec VPN. The analyst has configured both the ISAKMP and IPsec policies. What is the next step?","Apply the crypto map to the appropriate outbound interfaces."
"When an inbound Internet-traffic ACL is being implemented, what should be included to prevent the spoofing of internal networks?","ACEs to prevent traffic from private address spaces"
"Match the security term to the appropriate description. (Not all options are used.)",""
"Which two types of attacks are examples of reconnaissance attacks? (Choose two.)","port scan
ping sweep"
"Which Cisco solution helps prevent ARP spoofing and ARP poisoning attacks?","Dynamic ARP Inspection"
"When the Cisco NAC appliance evaluates an incoming connection from a remote device against the defined network policies, what feature is being used?","posture assessment"
"Which two steps are required before SSH can be enabled on a Cisco router? (Choose two.)",""
"The network administrator for an e-commerce website requires a service that prevents customers from claiming that legitimate orders are fake. What service provides this type of guarantee?",""
"Match the security technology with the description.",""
"What functionality is provided by Cisco SPAN in a switched network?","It mirrors traffic that passes through a switch port or VLAN to another port for traffic analysis."
"Which three statements are generally considered to be best practices in the placement of ACLs? (Choose three.)",""
"What function is performed by the class maps configuration object in the Cisco modular policy framework?","identifying interesting traffic"
"In an attempt to prevent network attacks, cyber analysts share unique identifiable attributes of known attacks with colleagues. What three types of attributes or indicators of compromise are helpful to share? (Choose three.)","IP addresses of attack servers
changes made to end system software
features of malware files"
"What two assurances does digital signing provide about code that is downloaded from the Internet? (Choose two.)","The code is authentic and is actually sourced by the publisher.
The code has not been modified since it left the software publisher."
"Refer to the exhibit. What algorithm is being used to provide public key exchange?","Diffie-Hellman"
"Which two statements describe the use of asymmetric algorithms? (Choose two.)",""
"Which statement is a feature of HMAC?","HMAC uses a secret key as input to the hash function, adding authentication to integrity assurance."
"What is the purpose of the webtype ACLs in an ASA?","to filter traffic for clientless SSL VPN users"
"Which two statements describe the effect of the access control list wildcard mask 0.0.0.15? (Choose two.)","The first 28 bits of a supplied IP address will be matched.
The last four bits of a supplied IP address will be ignored."
"Which type of firewall is the most common and allows or blocks traffic based on Layer 3, Layer 4, and Layer 5 information?","packet filtering firewall"
"Which protocol or measure should be used to mitigate the vulnerability of using FTP to transfer documents between a teleworker and the company file server?","SCP"
"Refer to the exhibit. The IPv6 access list LIMITED_ACCESS is applied on the S0/0/0 interface of R1 in the inbound direction. Which IPv6 packets from the ISP will be dropped by the ACL on R1?","ICMPv6 packets that are destined to PC1"
"What tool is available through the Cisco IOS CLI to initiate security audits and to make recommended configuration changes with or without administrator input?","Cisco AutoSecure"
"Refer to the exhibit. Which pair of crypto isakmp key commands would correctly configure PSK on the two routers?",""
"Which two technologies provide enterprise-managed VPN solutions? (Choose two.)","site-to-site VPN
remote access VPN"
"What are the three components of an STP bridge ID? (Choose three.)","the MAC address of the switch
the extended system ID
the bridge priority value"
"What are two differences between stateful and packet filtering firewalls? (Choose two.)","A stateful firewall provides more stringent control over security than a packet filtering firewall.
A stateful firewall will provide more logging information than a packet filtering firewall."
"Which portion of the Snort IPS rule header identifies the destination port?",""
"Match each SNMP operation to the corresponding description. (Not all options are used.)",""
"What port state is used by 802.1X if a workstation fails authorization?","unauthorized"
"Match the ASA special hardware modules to the description.",""
"Refer to the exhibit. Which two ACLs, if applied to the G0/1 interface of R2, would permit only the two LAN networks attached to R1 to access the network that connects to R2 G0/1 interface? (Choose two.)",""
"Which two characteristics apply to role-based CLI access superviews? (Choose two.)","A specific superview cannot have commands added to it directly.
Users logged in to a superview can access all commands specified within the associated CLI views."
"Match the IPS alarm type to the description.",""
"What are two security features commonly found in a WAN design? (Choose two.)","firewalls protecting the main and remote sites
VPNs used by mobile workers between sites"
'@

        $CSV_Array | Out-File .\Out.txt -Force

        $OutFile = @'
$TXTArray = (Get-Content .\Out.txt) -join "`n"
 
$QA_Array = (ConvertFrom-CSV -InputObject $TXTArray)
$i = 0
foreach ($obj in $QA_Array) {
    $SubAnswers = @()
    $SubAnswers += (($obj.Answer -split "`n") | Where-Object { $_ -notlike "" })
 
    try {
    if (($SubAnswers[0]+$SubAnswers[1]+$SubAnswers[2]).length -gt 123) {
    $CharAllotment = 41
    while ($SubAnswers.count -lt 3) {
    $SubAnswers += "-"
    }
    $NewSubAnswers = @()
    $SpareChars = 0
    foreach ($SubAnsw in $SubAnswers) {
    if ($SubAnsw.length -lt $CharAllotment) {
    $SpareChars += ($CharAllotment - ($SubAnsw.length))
    $NewSubAnswers += $SubAnsw
    }
    }
    foreach ($SubAnsw in ($SubAnswers | Where-Object { $_.length -gt $CharAllotment})) {
    $MaxChars = $CharAllotment + ($SpareChars / ($SubAnswers | Where-Object { $_.length -gt $CharAllotment}).count)
    $SubAnswShort = $SubAnsw.Substring(0, $MaxChars)
    $NewSubAnswers += $SubAnswShort
    }
    ($QA_Array[$i]).Answer = $NewSubAnswers
    } else {
    ($QA_Array[$i]).Answer = $SubAnswers
    }
} catch {
    Write-Output "Error for Question $i"
}
    $i++
}
 
 
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName('PresentationFramework') | Out-Null
 
$form = New-Object System.Windows.Forms.Form
$form.ShowInTaskbar = $false
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedToolWindow
$form.startposition = [System.Windows.Forms.FormStartPosition]::Manual
$form.Size = New-Object System.Drawing.Size(1, 1)
$form.location = New-Object System.Drawing.Point(0, 1440)
 
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$env:windir\explorer.exe")
 
[System.GC]::Collect()
 
 
function Get-Answer {
    param (
    )
    $Qtext = (Get-Clipboard | Out-String).trim()
    $Answer = @()
    try {
    $Answer += ($QA_Array | Where-Object { $_.Question -like "*$Qtext*" }).Answer
    while ($Answer.count -lt 3) {
    $Answer += "-"
    }
    }
    catch {
    $Answer = "No answer found"
    }
 
    Return $Answer
}
 
$Output = Get-Answer
 
$Menu_Restart = New-Object System.Windows.Forms.ToolStripMenuItem
$Menu_Restart.Text = "Restart..."
$Menu_Restart.Add_Click({ .\netseccaller.ps1 ; $Main_Tool_Icon.Dispose(); $form.dispose(); Exit -ErrorAction SilentlyContinue })
 
$Menu_Exit = New-Object System.Windows.Forms.ToolStripMenuItem
$Menu_Exit.Text = "Exit"
$Menu_Exit.Add_Click({ $Main_Tool_Icon.Dispose(); $form.dispose(); exit -ErrorAction SilentlyContinue })
 
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.ContextMenuStrip = New-Object System.Windows.Forms.ContextMenuStrip
$Main_Tool_Icon.ContextMenuStrip.items.Add($Menu_Restart) | Out-Null
$Main_Tool_Icon.ContextMenuStrip.items.Add($Menu_Exit) | Out-Null
$Main_Tool_Icon.Text = "cisco can suck my hairy balls"
$Main_Tool_Icon.Icon = $icon
$Main_Tool_Icon.Add_Click({
    $Output = Get-Answer
    $Main_Tool_Icon.Text = "$($Output[0])`n$($Output[1])`n$($Output[2])"
    })
$Main_Tool_Icon.Visible = $true
 
$form.ShowDialog() | Out-Null
'@


        $OutFile | Out-File .\netsectray.ps1 -Force
    }
    Process {
        Start-Process pwsh -WindowStyle hidden -ArgumentList '.\netsectray.ps1'
    }
    End {
    }
}

function Start-awsTray {
    [CmdletBinding()]
    param (
    )
    Begin {
    }
    Process {
        Get-ScheduledTask -TaskName 'PingCheckTray' -ErrorAction SilentlyContinue | Start-ScheduledTask
    }
    End {
    }
}

function Get-WinGetUpgrades {
    [Alias('gwgu')]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)][Alias('u')][Switch]$Upgrade
    )
    Begin {
        class Software {
            [string]$Name
            [string]$Id
            [string]$Version
            [string]$AvailableVersion
        }

        [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
        $Exclude = @(
            'WinDirStat 1.1.2',
            'Transmission Remote GUI 5.18',
            'VMware Workstation'
        )
        $AddToList = $true
    }
    Process {

        $upgradeResult = winget upgrade --include-unknown | Out-String

        if ($upgradeResult -match 'No installed package found matching input criteria\.') {
            Return 'No upgrades available'
        }

        $lines = $upgradeResult.Split([Environment]::NewLine)


        # Find the line that starts with Name, it contains the header
        $fl = 0
        while (-not $lines[$fl].StartsWith('Name')) {
            $fl++
        }

        # Line $i has the header, we can find char where we find ID and Version
        $idStart = $lines[$fl].IndexOf('Id')
        $versionStart = $lines[$fl].IndexOf('Version')
        $availableStart = $lines[$fl].IndexOf('Available')
        $sourceStart = $lines[$fl].IndexOf('Source')

        # Now cycle in real package and split accordingly
        $upgradeList = @()
        For ($i = $fl + 1; $i -le $lines.Length; $i++) {
            $line = $lines[$i]
            if ($line.Length -gt ($availableStart + 1) -and -not $line.StartsWith('-')) {
                $name = $line.Substring(0, $idStart).TrimEnd()
                $id = $line.Substring($idStart, $versionStart - $idStart).TrimEnd()
                $version = $line.Substring($versionStart, $availableStart - $versionStart).TrimEnd()
                $available = $line.Substring($availableStart, $sourceStart - $availableStart).TrimEnd()
                $software = [Software]::new()
                $software.Name = $name
                $software.Id = $id
                $software.Version = $version
                $software.AvailableVersion = $available

                foreach ($Exclusion in $Exclude) {
                    if ("*$Exclusion*" -like "*$name*") {
                        $AddToList = $false
                    }
                }
                if ($AddToList) {
                    $upgradeList += $software
                }
                $AddToList = $true
            }
        }


    }
    End {
        if ($Upgrade) {
            $PacksToUpg = $upgradeList | Out-ConsoleGridView -Title 'Choose packages to upgrade'
            $PacksToUpg | ForEach-Object {
                Write-Color "`nUpgrading package: ", "$($_.Name)", " [$($_.Version) -> ", "$($_.AvailableVersion)", "]`n" -Color White, Yellow, White, Green, White
                winget upgrade --id $_.Id --accept-package-agreements --include-unknown --silent
            }
            Return
        }
        else {
            Return $upgradeList
        }
    }
}

function ConvertTo-SRT {
    <#
        .SYNOPSIS
        Fast conversion of Microsoft Stream VTT subtitle file to SRT format.
        .DESCRIPTION
        Uses select-string instead of get-content to improve speed 2 magnitudes.
        .PARAMETER Path
        Specifies the path to the VTT text file (mandatory).
        .PARAMETER OutFile
        Specifies the path to the output SRT text file (defaults to input file with .srt).
        .EXAMPLE
        ConvertTo-SRT -Path .\caption.vtt
        .EXAMPLE
        ConvertTo-SRT -Path .\caption.vtt -OutFile .\SRT\caption.srt
        .EXAMPLE
        Get-Item caption*.vtt | ConvertTo-SRT
        .EXAMPLE
        ConvertTo-SRT -Path ('.\caption.vtt','.\caption.vtt','.\caption3.vtt')
        .EXAMPLE
        ('.\caption.vtt','.\caption2.vtt','.\caption3.vtt') | ConvertTo-SRT
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Path to VTT file.')]
        [Alias('PSPath')]
        [ValidateNotNullOrEmpty()]
        [Object[]]$Path,

        [Parameter(Mandatory = $false,
            Position = 1,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Path to output SRT file.')]
        [string]$OutFile
    )

    process {
        foreach ($File in $Path) {
            $Lines = @()
            if ( $File.FullName ) {
                $VTTFile = $File.FullName
            }
            else {
                $VTTFile = $File
            }

            if ( -not($PSBoundParameters.ContainsKey('OutFile')) ) {
                $OutFile = $VTTFile -replace '(\.vtt$|\.txt$)', '.srt'
                if ( $OutFile.split('.')[-1] -ne 'srt' ) {
                    $OutFile = $OutFile + '.srt'
                }
            }

            New-Item -Path $OutFile -ItemType File -Force | Out-Null
            $Subtitles = Select-String -Path $VTTFile -Pattern '(^|\s)(\d\d):(\d\d):(\d\d)\.(\d{1,3})' -Context 0, 2

            for ($i = 0; $i -lt $Subtitles.count; $i++) {
                $Lines += $i + 1
                $Lines += $Subtitles[$i].line -replace '\.', ','
                $Lines += $Subtitles[$i].Context.DisplayPostContext
                $Lines += ''
            }

            $Lines | Out-File -FilePath $OutFile -Append -Force
        }
    }
}

Function ConvertTo-Icon {
    <#
.Synopsis
    Converts .PNG images to icons
.Description
    Converts a .PNG image to an icon
.Example
    ConvertTo-Icon -Path .\Logo.png -Destination .\Favicon.ico
#>

    [CmdletBinding()]
    param(
        # The file
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)]
        [Alias('Fullname', 'File', 'F', 'P')]
        [string]$Path,

        # If provided, will output the icon to a location
        [Parameter(Position = 1, ValueFromPipelineByPropertyName = $true)]
        [Alias('OutputFile', 'O', 'D')]
        [string]$Destination
    )

    Begin {

        $TypeDefinition = @'
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
 
/// <summary>
/// Adapted from this gist: https://gist.github.com/darkfall/1656050
/// Provides helper methods for imaging
/// </summary>
public static class ImagingHelper
{
    /// <summary>
    /// Converts a PNG image to a icon (ico) with all the sizes windows likes
    /// </summary>
    /// <param name="inputBitmap">The input bitmap</param>
    /// <param name="output">The output stream</param>
    /// <returns>Wether or not the icon was succesfully generated</returns>
    public static bool ConvertToIcon(Bitmap inputBitmap, Stream output)
    {
        if (inputBitmap == null)
            return false;
 
        int[] sizes = new int[] { 256, 48, 32, 16 };
 
        // Generate bitmaps for all the sizes and toss them in streams
        List<MemoryStream> imageStreams = new List<MemoryStream>();
        foreach (int size in sizes)
        {
            Bitmap newBitmap = ResizeImage(inputBitmap, size, size);
            if (newBitmap == null)
                return false;
            MemoryStream memoryStream = new MemoryStream();
            newBitmap.Save(memoryStream, ImageFormat.Png);
            imageStreams.Add(memoryStream);
        }
 
        BinaryWriter iconWriter = new BinaryWriter(output);
        if (output == null || iconWriter == null)
            return false;
 
        int offset = 0;
 
        // 0-1 reserved, 0
        iconWriter.Write((byte)0);
        iconWriter.Write((byte)0);
 
        // 2-3 image type, 1 = icon, 2 = cursor
        iconWriter.Write((short)1);
 
        // 4-5 number of images
        iconWriter.Write((short)sizes.Length);
 
        offset += 6 + (16 * sizes.Length);
 
        for (int i = 0; i < sizes.Length; i++)
        {
            // image entry 1
            // 0 image width
            iconWriter.Write((byte)sizes[i]);
            // 1 image height
            iconWriter.Write((byte)sizes[i]);
 
            // 2 number of colors
            iconWriter.Write((byte)0);
 
            // 3 reserved
            iconWriter.Write((byte)0);
 
            // 4-5 color planes
            iconWriter.Write((short)0);
 
            // 6-7 bits per pixel
            iconWriter.Write((short)32);
 
            // 8-11 size of image data
            iconWriter.Write((int)imageStreams[i].Length);
 
            // 12-15 offset of image data
            iconWriter.Write((int)offset);
 
            offset += (int)imageStreams[i].Length;
        }
 
        for (int i = 0; i < sizes.Length; i++)
        {
            // write image data
            // png data must contain the whole png data file
            iconWriter.Write(imageStreams[i].ToArray());
            imageStreams[i].Close();
        }
 
        iconWriter.Flush();
 
        return true;
    }
 
    /// <summary>
    /// Converts a PNG image to a icon (ico)
    /// </summary>
    /// <param name="input">The input stream</param>
    /// <param name="output">The output stream</param
    /// <returns>Wether or not the icon was succesfully generated</returns>
    public static bool ConvertToIcon(Stream input, Stream output)
    {
        Bitmap inputBitmap = (Bitmap)Bitmap.FromStream(input);
        return ConvertToIcon(inputBitmap, output);
    }
 
    /// <summary>
    /// Converts a PNG image to a icon (ico)
    /// </summary>
    /// <param name="inputPath">The input path</param>
    /// <param name="outputPath">The output path</param>
    /// <returns>Wether or not the icon was succesfully generated</returns>
    public static bool ConvertToIcon(string inputPath, string outputPath)
    {
        using (FileStream inputStream = new FileStream(inputPath, FileMode.Open))
        using (FileStream outputStream = new FileStream(outputPath, FileMode.OpenOrCreate))
        {
            return ConvertToIcon(inputStream, outputStream);
        }
    }
 
 
 
    /// <summary>
    /// Converts an image to a icon (ico)
    /// </summary>
    /// <param name="inputImage">The input image</param>
    /// <param name="outputPath">The output path</param>
    /// <returns>Wether or not the icon was succesfully generated</returns>
    public static bool ConvertToIcon(Image inputImage, string outputPath)
    {
        using (FileStream outputStream = new FileStream(outputPath, FileMode.OpenOrCreate))
        {
            return ConvertToIcon(new Bitmap(inputImage), outputStream);
        }
    }
 
 
    /// <summary>
    /// Resize the image to the specified width and height.
    /// Found on stackoverflow: https://stackoverflow.com/questions/1922040/resize-an-image-c-sharp
    /// </summary>
    /// <param name="image">The image to resize.</param>
    /// <param name="width">The width to resize to.</param>
    /// <param name="height">The height to resize to.</param>
    /// <returns>The resized image.</returns>
    public static Bitmap ResizeImage(Image image, int width, int height)
    {
        var destRect = new Rectangle(0, 0, width, height);
        var destImage = new Bitmap(width, height);
 
        destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
 
        using (var graphics = Graphics.FromImage(destImage))
        {
            graphics.CompositingMode = CompositingMode.SourceCopy;
            graphics.CompositingQuality = CompositingQuality.HighQuality;
            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.SmoothingMode = SmoothingMode.HighQuality;
            graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
 
            using (var wrapMode = new ImageAttributes())
            {
                wrapMode.SetWrapMode(WrapMode.TileFlipXY);
                graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
            }
        }
 
        return destImage;
    }
}
'@


        Add-Type -TypeDefinition $TypeDefinition -ReferencedAssemblies 'System.Drawing', 'System.IO', 'System.Collections', 'System.Drawing.Common', 'System.Drawing.Primitives'

        If (-Not 'ImagingHelper' -as [Type]) {
            Throw 'The custom "ImagingHelper" type is not loaded'
        }
    }

    Process {
        foreach ($Item in $Path) {
            #region Resolve Path
            $ResolvedFile = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Item)
            If (-not $ResolvedFile) {
                return
            }
            if ($Destination) {
                $OutPath = "$Destination\$((Get-Item $ResolvedFile[0]).BaseName).ico"
            }
            else {
                $OutPath = "$((Get-Item $ResolvedFile[0]).DirectoryName)\$((Get-Item $ResolvedFile[0]).BaseName).ico"
            }
            #endregion

            [ImagingHelper]::ConvertToIcon($ResolvedFile[0].Path, $OutPath)
        }
    }
    End {
    }
}

$InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()

function Invoke-Async {
    <#
            .SYNOPSIS
            Runs code, with variables, asynchronously
 
            .DESCRIPTION
            This function runs the given code in an asynchronous runspace.
            This lets you process data in the background while leaving the UI responsive to input
 
            .PARAMETER Code
            The code to run in the runspace
 
            .PARAMETER Variables
            A hashtable containing variable names and values to pass into the runspace
 
            .EXAMPLE
 
            $AsyncParameters = @{
                Variables = @{
                    Key1 = 'Value1'
                    Key2 = $SomeOtherVariable
                }
                Code = {
                    Write-Host "Key1: $Key1`nKey2: $Key2"
                }
            }
            Run-Async @AsyncParameters
 
            .NOTES
            It's more reliable to pass single values than complex objects duje to the way PowerShell handles value/reference passing with objects
 
            .INPUTS
            Variables, Code
 
            .OUTPUTS
            None
        #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ScriptBlock]
        $Code,
        [Parameter(Mandatory = $false, Position = 1, ValueFromPipeline = $true)]
        [hashtable]
        $Variables
    )
    # Add the above code to a runspace and execute it.
    $PSinstance = [powershell]::Create() #| Out-File -Append -FilePath $LogFile
    $PSinstance.Runspace = [runspacefactory]::CreateRunspace($InitialSessionState)
    $PSinstance.Runspace.ApartmentState = "STA"
    $PSinstance.Runspace.ThreadOptions = "UseNewThread"
    $PSinstance.Runspace.Open()
    if ($Variables) {
        # Pass in the specified variables from $VariableList
        $Variables.keys.ForEach({
                $PSInstance.Runspace.SessionStateProxy.SetVariable($_, $Variables.$_)
            })
    }
    $PSInstance.AddScript($Code)
    $PSinstance.BeginInvoke()
}
function Add-FMSignature {
    [Alias('signfm')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline)][Alias('f')]$File
    )
    Begin {
        $CodeSignCert = Get-Item -Path 'Microsoft.PowerShell.Security\Certificate::CurrentUser\My\E992867E7D48FBFE439C9909B35E12244133A72D'
    }
    Process {
        $FilePath = Convert-Path -Path $File
        Try {
            Set-AuthenticodeSignature -FilePath $FilePath -Certificate $CodeSignCert -TimestampServer 'http://timestamp.digicert.com' -ErrorAction Stop
        }
        Catch {
            Write-Error $_
        }
    }
    End {
        Return
    }
}

function Get-FileName {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $false)]
        [string]$WindowTitle = 'Open File',

        [Parameter(Mandatory = $false)]
        [string]$InitialDirectory = "",

        [Parameter(Mandatory = $false)]
        [string]$Filter = "All files (*.*)|*.*",

        [Alias('f')][switch]$Folder
    )
    Add-Type -AssemblyName System.Windows.Forms
    if (!$InitialDirectory) {
        if ('Success' -notmatch ((Test-Connection 192.168.0.200 -Count 2 -timeout 1).Status)) {
            $InitialDirectory = "D:\Plex"
        }
        else {
            $InitialDirectory = "\\aws-server\D\Plex"
        }
    }

    if ($Folder) {
        $openFileDialog = New-Object System.Windows.Forms.FolderBrowserDialog
    }
    else {
        $openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
        $openFileDialog.Title = $WindowTitle
        $openFileDialog.Filter = $Filter
        $openFileDialog.CheckFileExists = $true
        $openFileDialog.MultiSelect = $true
    }

    if (![string]::IsNullOrWhiteSpace($InitialDirectory)) {
        $openFileDialog.InitialDirectory = $InitialDirectory
    }

    if ($openFileDialog.ShowDialog().ToString() -eq 'OK') {
        if ($Folder) {
            $selected = $openFileDialog.SelectedPath
        }
        else {
            $selected = @($openFileDialog.Filenames)
        }
    }

    # clean-up
    $openFileDialog.Dispose()

    return $selected
}

function New-Subs {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $false, Position = 0)][Alias('l')][String]$Language = 'English',
        [Parameter(Mandatory = $false, Position = 1)][Alias('m')][String]$Model = 'large-v2',
        [Parameter(Mandatory = $false, Position = 2)][Alias('b')][int]$Batch = 1,
        [Alias('f')][switch]$Folder,
        [Alias('t')][switch]$Translate,
        [Alias('d')][switch]$Distil
    )

    $CodeArray = @()

    if ($Distil) {
        $Model = "distil-$Model"
    }

    if ($Translate) {
        $Task = 'translate'
        $Model = 'large-v2'
    }
    else {
        $Task = 'transcribe'
    }

    if ($Folder) {
        for ($i = 0; $i -lt $Batch; $i++) {
            $FolderObj = Get-FileName -Folder
            $CodeString = "S:\Utilities\Whisper-Faster\whisper-faster.exe ""$FolderObj"" --language=$Language --model=""$Model"" --beep_off -br --skip --standard --task=$Task"
            $CodeBlock = [ScriptBlock]::Create($CodeString)
            $CodeArray += $CodeBlock
        }
    }
    else {
        for ($i = 0; $i -lt $Batch; $i++) {
            $Files = Get-FileName
            foreach ($File in $Files) {
                $CodeString = "S:\Utilities\Whisper-Faster\whisper-faster.exe ""$File"" --language=$Language --model=""$Model"" --beep_off -br --skip --standard --task=$Task"
                $CodeBlock = [ScriptBlock]::Create($CodeString)
                $CodeArray += $CodeBlock
            }
        }
    }

    foreach ($CodeObj in $CodeArray) {
        Invoke-Command -ScriptBlock $CodeObj
    }
    Return
}

function Move-Enc {
    [CmdletBinding()]
    Param (
    )
    $ErrorActionPreference = 'Continue'
    Install-CustomModule PSWriteColor
    [Int64]$OldTotalSpace = 0
    [Int64]$NewTotalSpace = 0
    $ToBeDeleted = @()
    $FolderObj = Get-FileName -Folder
    $ReEncFolders = Get-ChildItem -Path $FolderObj -Directory -Recurse -Filter ".reencode"

    function Move-ItemRetry {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline)][Alias('f')]$File,
            [Parameter(Mandatory = $true, Position = 1)][Alias('d')]$Destination
        )
        if ($File.GetType().Name -eq 'String') {
            Try {
                $File = Get-Item -Path $File
            }
            Catch {
                Throw "File not found: $File"
            }
        }
        if ($Destination.GetType().Name -eq 'String') {
            Try {
                $Destination = Get-Item -Path $Destination
            }
            Catch {
                Throw "Destination not found: $Destination"
            }
        }

        $i = 0
        $break = $false
        do {
            Try {
                $NewFile = $File | Move-Item -Destination $Destination.FullName -Force -ErrorAction Stop -PassThru
                $break = $true
            }
            Catch {
                $ErrorMessage = $_.Exception.Message
                Start-Sleep -Seconds 5
                $i++
            }
        }
        until ($i -ge 6 -or $break)

        if ($i -ge 6 -and !$break) {
            Throw "Failed to move file: $($File.FullName)`n`n$ErrorMessage"
        }
        else {
            Return $NewFile
        }
    }

    foreach ($Folder in $ReEncFolders) {
        $ReEncFiles = Get-ChildItem -Path $Folder.FullName -File -Recurse
        [Int64]$OldFolderSize = 0
        [Int64]$NewFolderSize = 0

        if ($ReEncFiles.Count -gt 0) {
            $ParentFolderContents = Get-ChildItem -Path $Folder.Parent.FullName -File | Where-Object { $_.extension -notin @(".srt", ".nfo", ".jpg", ".jpeg", ".png", ".txt", ".bmp") }
            $OldFilesFolderPath = $Folder.Parent.FullName + "\.old"
            Try {
                $OldFilesFolder = New-Item -Path $Folder.Parent.FullName -Name ".old" -ItemType Directory -ErrorAction Stop
            }
            Catch {
                $OldFilesFolder = Get-Item -Path $OldFilesFolderPath
            }

            foreach ($File in $ReEncFiles) {
                $OldFile = $ParentFolderContents | Where-Object { $_.BaseName -match [RegEx]::Escape("$($File.BaseName)") } | Sort-Object Length -Descending | Select-Object -First 1
                [Double]$FileSize = $([Math]::Round(($File.Length / 1MB), 2))
                [Double]$OldFileSize = $([Math]::Round(($OldFile.Length / 1MB), 2))

                $OldFolderSize += $File.Length
                $NewFolderSize += $OldFile.Length
                $NewTotalSpace += $File.Length
                $OldTotalSpace += $OldFile.Length

                Try {
                    $OldFile = $OldFile | Move-ItemRetry -Destination $OldFilesFolder.FullName -ErrorAction Stop
                    $i = 0
                    Try {
                        $File | Move-ItemRetry -Destination $Folder.Parent.FullName -ErrorAction Stop > $null
                        Write-Color "`n Replaced", " $(($OldFile.Name).TrimStart("$SplitString"))", " with x265 re-encoded .mkv `n [size: ", "$OldFileSize MB", " -> ", "$FileSize MB", "]`n" -Color Gray, Yellow, Gray, Red, Gray, Green, Gray
                    }
                    Catch {
                        Write-Color " Handbrake is still processing ", "$(($File.Name).TrimStart("$SplitString"))", " - reencode and original file both skipped.`n" -Color White, Yellow, White
        
                        Try {
                            $OldFile | Move-ItemRetry -Destination $Folder.Parent.FullName -ErrorAction Stop > $null
                        }
                        Catch {
                            Write-Error $_
                        }
                    }
                }
                Catch {
                    Write-Error $_
                }
            }
            $ToBeDeleted += $OldFilesFolder
            if ($((Get-ChildItem $Folder).Count) -eq 0) {

                Try {
                    $Folder | Remove-Item -Force -ErrorAction Stop
                    Write-Color " Removing empty folder: ", "$(($Folder.FullName).TrimStart('D:\Plex'))`n" -Color Gray, Yellow
                }
                Catch {
                    Write-Color " Handbrake is still processing ", "$(($Folder.FullName).TrimStart('D:\Plex'))", " - '.reencode'-folder will be left intact.`n" -Color White, Yellow, White
                }
            }
            else {
                Write-Color " Handbrake is still processing ", "$(($Folder.FullName).TrimStart('D:\Plex'))", " - '.reencode'-folder will be left intact.`n" -Color White, Yellow, White
            }
            Write-Color "`n Space saved in \$($Folder.Parent.Parent.Name)\$($Folder.Parent.Name): ", "$([Math]::Round(($OldFolderSize - $NewFolderSize)/1GB, 2)) GB`n" -Color Gray, Yellow
        }
    }
    Write-Color "`n Total file size (before/after): ", "$([Math]::Round(($OldTotalSpace)/1GB, 2))) GB", " -> ", "$([Math]::Round(($NewTotalSpace)/1GB, 2)) GB" -Color White, Red, White, Green
    Write-Color "`n Total space saved: ", "$([Math]::Round(($OldTotalSpace - $NewTotalSpace)/1GB, 2)) GB`n" -Color White, Yellow
    Write-Color "`n Delete original encodes (only succesfully processed files will be touched)? [", "Y", "/", "N", "]`n" -Color White, Green, White, Red, White
    $Break = $false
    while (!$Break) {
        if ([Console]::KeyAvailable -eq $True) {
            $Key = [Console]::ReadKey("NoEcho,IncludeKeyDown")
            switch ($Key.Key) {
                'Y' {
                    $DeleteFiles = $true ; $Break = $true 
                }
                'N' {
                    $DeleteFiles = $false ; $Break = $true 
                }
                Default { 
                }
            }
        }    
    }
    if ($DeleteFiles) {
        $ToBeDeleted | ForEach-Object { Remove-Item -Path $_.FullName -Recurse -Force }
    }
    Return
}