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 -LiteralPath $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 -LiteralPath $outputpath -Filter *.txt | Sort-Object lastwritetime)[-1]).IndexOf('### Response:') + 2) $CurrentFile = (Get-ChildItem -LiteralPath $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 -LiteralPath $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 -LiteralPath "$OutputPath\Trim").Count $RunningLLMs = (Get-Process | Where-Object { $_.ProcessName -like 'main' -or $_.ProcessName -like 'falcon_main' }).Count $Texts = (Get-ChildItem -LiteralPath $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 -LiteralPath "$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 -LiteralPath '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 { [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 -LiteralPath '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 -LiteralPath '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 -LiteralPath '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 -LiteralPath $env:logs $Output = '' foreach ($Log in $Logs) { $LogSize = [math]::Round(((Get-ChildItem -LiteralPath $Log.fullname | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1KB), 2) $LogSizeMB = [math]::Round(((Get-ChildItem -LiteralPath $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 -LiteralPath $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 -LiteralPath $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 -LiteralPath "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)][Alias('in', 'i')]$Infile, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 1)][Alias('out', 'o')]$Outfile, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 2)][Alias('format')][string]$Container = 'mkv', [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 3)][ValidateRange(1, 7)][Alias('p')][int]$Preset = 1, [Parameter(Mandatory = $false, ValueFromPipeline = $false)][Alias('ow')][switch]$Overwrite, [Parameter(Mandatory = $false, ValueFromPipeline = $false)][Alias('rip')][switch]$AudioRip, [Parameter(Mandatory = $false, ValueFromPipeline = $false)][Alias('audioreplace')][switch]$ReplaceAudio, [Parameter(Mandatory = $false, ValueFromPipeline = $false)][switch]$Copy ) Begin { if ($Format -notmatch '^\.') { $Format = ".$Format" } if ($ReplaceAudio) { if ($Infile.Count -lt 2) { Throw "`nTo replace audio in file, pass two input files (video first, then audio).`n" } else { $VidIn = Get-Item $Infile[0] $AudIn = Get-Item $Infile[1] if (!$Outfile) { $Out = ((($AudIn.PSParentPath).Replace('Microsoft.PowerShell.Core\FileSystem::', '')) + '\' + "$($AudIn.BaseName)" + "$Format") if ($Out -eq $AudIn.FullName) { $Out = ((($AudIn.PSParentPath).Replace('Microsoft.PowerShell.Core\FileSystem::', '')) + '\' + "$($AudIn.BaseName)" + "_new$Format") } } else { $Out = $Outfile } $cmd = "ffmpeg -i ""$($VidIn.FullName)"" -i ""$($AudIn.FullName)"" -c:v copy -c:a aac -map 0:v:0 -map 1:a:0 ""$Out""" } } $i = 0 $PresetInt = $Preset + 11 } Process { if (!$cmd) { Try { if (($Infile.GetType().Name) -like 'String') { $Infile = Get-Item -LiteralPath $Infile } $In = $Infile.FullName } Catch { Throw "`nInput must be a string or FileInfo object.`n" } if (!$Outfile -and ($Format -eq $Infile.Extension)) { $Out = ((($Infile.PSParentPath).Replace('Microsoft.PowerShell.Core\FileSystem::', '')) + '\' + "$($Infile.BaseName)" + "$Format").Replace("$($InFile.BaseName)", "$($InFile.BaseName)_2") } elseif (!$Outfile) { $Out = ((($Infile.PSParentPath).Replace('Microsoft.PowerShell.Core\FileSystem::', '')) + '\' + "$($Infile.BaseName)" + "$Format") } else { $Out = $Outfile } if ($Copy) { $cmd = "ffmpeg -i ""$In"" -c copy ""$Out""" } elseif ($AudioRip) { $cmd = "ffmpeg -y -v error -i ""$In"" -vn -acodec copy ""$Out""" } else { $cmd = "ffmpeg -i ""$In"" -c:v hevc_nvenc -preset $PresetInt -rc vbr -cq 0 -qmin:v 28 -qmax:v 32 ""$Out""" } $OverwriteCMD = "Remove-Item ""$In""" } # Write-Host "`n`n Command: " -NoNewline # Write-Host "$cmd`n" -ForegroundColor Yellow Invoke-Expression $cmd -ErrorAction Stop if ($Overwrite -and !$AudioRip -and !$ReplaceAudio) { Invoke-Expression $OverwriteCMD } $i++ Write-Host "`n`n Output: " -NoNewline Write-Host "$Out`n" -ForegroundColor Yellow } End { Return " Total files processed: $i`n`n" } } 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-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++ } # Find the line that begins with "X upgrades available", it marks the end of the list $ll = 0 while (-not ($lines[$ll] -match '^[0-9]+ upgrades available\.')) { $ll++ } # 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 $ll; $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 New-Remux { [Alias('remux')] [CmdletBinding()] param ( [Parameter(Mandatory = $false, Position = 0)][String]$Path, [Parameter(Mandatory = $false, Position = 1)][Alias('Input', 'i')][String]$InputFormat = 'mp?', [Parameter(Mandatory = $false, Position = 2)][Alias('Output', 'o')][String]$OutputFormat = 'mkv', [Parameter(Mandatory = $false)][Alias('Keep')][Switch]$NoOverwrite, [Parameter(Mandatory = $false)][Switch]$Movies, [Parameter(Mandatory = $false)][Switch]$TV, [Parameter(Mandatory = $false)][Switch]$All ) Begin { $i = 0 $TBD = @() $Remuxed = @() $ext = "$OutputFormat" if ($TV) { $OldFiles = Get-ChildItem -LiteralPath 'D:\Plex\TV\' -Recurse -Filter "*.$InputFormat" } elseif ($Movies) { $OldFiles = Get-ChildItem -LiteralPath 'D:\Plex\Movies\' -Recurse -Filter "*.$InputFormat" } elseif ($All) { $OldFiles = Get-ChildItem -LiteralPath 'D:\Plex\TV\' -Recurse -Filter "*.$InputFormat" $OldFiles += Get-ChildItem -LiteralPath 'D:\Plex\Movies\' -Recurse -Filter "*.$InputFormat" } else { if (!$Path) { $Path = Get-FileName } if ((Get-Item -LiteralPath $Path).Attributes -notmatch 'Directory') { $OldFiles = Get-Item -LiteralPath $Path } else { $OldFiles = Get-ChildItem -LiteralPath "$Path" -Recurse -Filter "*.$InputFormat" } } } Process { if ($OldFiles) { $OldFiles | ForEach-Object { ffmpeg -y -fflags +genpts -i "$($_.fullname)" -c:a copy -c:v copy "$($_.directory.fullname)\$($_.basename).$ext" $i++ Write-Host "`n`n [$i/$($OldFiles.Count)] " -ForegroundColor Yellow -NoNewline if ((Get-Item -LiteralPath "$($_.directory.fullname)\$($_.basename).$ext" -ErrorAction SilentlyContinue).Length -gt ($_.Length * 0.9)) { Write-Host "Finished processing $($_.Name)" $TBD += $_ $Remuxed += $_ } else { Write-Host "Error processing $($_.Name)" -ForegroundColor Red $TBD += (Get-Item -LiteralPath "$($_.directory.fullname)\$($_.basename).$ext" -ErrorAction SilentlyContinue) } Write-Host "`n" } } } End { if ($OldFiles) { $i = 0 Write-Host "Finished remuxing the following files into .$($OutputFormat):`n" -ForegroundColor Green $Remuxed | ForEach-Object { $i++ Write-Host " [$i/$($Remuxed.Count)] " -ForegroundColor Yellow -NoNewline Write-Host "$($_.Name)" } $Errored = $(($TBD | Where-Object { $_.extension -match $ext }).Name) if ($Errored.count -gt 0) { Write-Host " Errors processing the following files:`n" -ForegroundColor Red $Errored } if (!$NoOverwrite) { $TBD | Remove-Item } } else { Write-Host "`n No files matching input parameters found.`n" -ForegroundColor Yellow } } } 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 -LiteralPath $ResolvedFile[0]).BaseName).ico" } else { $OutPath = "$((Get-Item -LiteralPath $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 -LiteralPath '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 = '', [Parameter(Mandatory = $false)] [string]$InitialDirectory = '', [Parameter(Mandatory = $false)] [string]$Filter = 'All files (*.*)|*.*', [Alias('f')][switch]$Folder ) Add-Type -AssemblyName System.Windows.Forms if (!$WindowTitle) { if ($Folder) { $WindowTitle = 'Select Folder(s)' } else { $WindowTitle = 'Select File(s)' } } if (!$InitialDirectory) { $InitialDirectory = [Environment]::GetFolderPath('UserProfile') } if ($Folder) { $openFileDialog = [System.Windows.Forms.FolderBrowserDialog]@{ InitialDirectory = $InitialDirectory Description = $WindowTitle UseDescriptionForTitle = $true Multiselect = $true } } else { $openFileDialog = [System.Windows.Forms.OpenFileDialog]@{ InitialDirectory = $InitialDirectory Title = $WindowTitle Filter = $Filter CheckFileExists = $true Multiselect = $true } } if ($openFileDialog.ShowDialog().ToString() -eq 'OK') { if ($Folder) { $Selected = @($openFileDialog.SelectedPaths) } else { $Selected = @($openFileDialog.Filenames) } } $openFileDialog.Dispose() return $Selected } function Move-Enc { [CmdletBinding()] Param ( [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline)][Alias('p')]$Path ) Begin { 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 -LiteralPath $File } Catch { Throw "File not found: $File" } } if ($Destination.GetType().Name -eq 'String') { Try { $Destination = Get-Item -LiteralPath $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 } } $ErrorActionPreference = 'Continue' Install-CustomModule PSWriteColor [Int64]$OldTotalSpace = 0 [Int64]$NewTotalSpace = 0 $ToBeDeleted = @() $LargerThanOriginals = @() $ReEncFolders = @() if ($Path) { $PathsArray = @($Path) } else { $PathsArray = @(Get-FileName -Folder) } } Process { foreach ($FolderObj in $PathsArray) { $ReEncFolders += Get-ChildItem -LiteralPath $FolderObj -Directory -Recurse -Filter '.reencode' } if ($ReEncFolders.Count -gt 0) { foreach ($FolderObj in $PathsArray) { foreach ($Folder in $ReEncFolders) { $ReEncFiles = Get-ChildItem -LiteralPath $Folder.FullName -File -Recurse [Int64]$OldFolderSize = 0 [Int64]$NewFolderSize = 0 if ($ReEncFiles.Count -gt 0) { $ParentFolderContents = Get-ChildItem -LiteralPath $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 -LiteralPath $OldFilesFolderPath } foreach ($File in $ReEncFiles) { $OldFile = $ParentFolderContents | Where-Object { $_.BaseName -match [RegEx]::Escape("$(($File.BaseName -split ('\([0-9]\)$'))[0])") } | Sort-Object Length -Descending | Select-Object -First 1 [Double]$FileSize = $([Math]::Round(($File.Length / 1MB), 2)) [Double]$OldFileSize = $([Math]::Round(($OldFile.Length / 1MB), 2)) if ($FileSize -lt $OldFileSize) { $OldFolderSize += $OldFile.Length $NewFolderSize += $File.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 $_ } } else { Write-Color "`n Skipping", " $(($File.Name).TrimStart("$SplitString"))", " - x265 re-encoded file is larger than original file `n [size: ", "$OldFileSize MB", ' -> ', "$FileSize MB", "]`n" -Color Gray, Yellow, Gray, Green, Gray, Red, Gray $File | Add-Member -MemberType NoteProperty -Name 'OldSize' -Value $OldFileSize $File | Add-Member -MemberType NoteProperty -Name 'NewSize' -Value $FileSize $LargerThanOriginals += $File } } $ToBeDeleted += $OldFilesFolder Write-Color "`n Space saved in \$($Folder.Parent.Parent.Name)\$($Folder.Parent.Name): ", "$([Math]::Round(($OldFolderSize - $NewFolderSize)/1GB, 2)) GB`n" -Color Gray, Yellow } } } } } End { if ($ReEncFolders.Count -gt 0) { 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 -ShowTime Write-Color "`n Total space saved: ", "$([Math]::Round(($OldTotalSpace - $NewTotalSpace)/1GB, 2)) GB`n" -Color White, Yellow if ($LargerThanOriginals.Count -gt 0) { Write-Color "`n The following re-encodes have been skipped due to a larger file size than the originals:`n" -Color White $LargerThanOriginals | ForEach-Object { Write-Color " $($_.Name) `n ", '[', "$([Math]::Round(($_.NewSize - $_.OldSize), 2)) MB", " larger than original]`n" -Color Yellow, White, Red, White } } Write-Color "`n Delete replaced encodes (as well as re-encodes with a larger file size than the originals)? [", 'Y', '/', 'N', "]`n" -Color White, Green, White, Red, White $DeleteFilesInput = Read-Host 'Input' switch ($DeleteFilesInput.ToUpper()) { 'Y' { $DeleteFiles = $true } Default { $DeleteFiles = $false } } if ($DeleteFiles) { $LargerThanOriginals | ForEach-Object { Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue } $ToBeDeleted | ForEach-Object { Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue } foreach ($Folder in $ReEncFolders) { if ($((Get-ChildItem $Folder).Count) -eq 0) { Try { $Folder | Remove-Item -Force -ErrorAction Stop } 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 } } } Return } else { Write-Warning "No '.reencode'-folders found in the specified path(s)." Write-Output "`n Press any key to exit..." [Console]::ReadKey('NoEcho,IncludeKeyDown') > $null Return } } } function Get-YesNo { param( [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)][Alias('M')][String]$Message, [Parameter(Mandatory = $false, Position = 1)][Alias('C')][String]$Color = 'Yellow' ) if ($Message) { Write-Host "`n$Message`n" -ForegroundColor $Color } Write-Host '[' -NoNewline Write-Host 'Y' -ForegroundColor DarkGreen -NoNewline Write-Host '/' -NoNewline Write-Host 'N' -ForegroundColor DarkRed -NoNewline Write-Host ']' -NoNewline $Loop = $True while ($Loop) { if ([Console]::KeyAvailable -eq $True) { $Key = [Console]::ReadKey('NoEcho,IncludeKeyDown') switch ($Key.Key) { 'Y' { $Answer = $True $Loop = $False } 'N' { $Answer = $False $Loop = $False } default { Write-Host "`r[" -NoNewline Write-Host 'Y' -ForegroundColor DarkGreen -NoNewline Write-Host '/' -NoNewline Write-Host 'N' -ForegroundColor DarkRed -NoNewline Write-Host ']' -NoNewline Write-Host ' - ' -NoNewline Write-Host 'Invalid input. Please try again.' -ForegroundColor Yellow -BackgroundColor DarkRed -NoNewline } } } else { Start-Sleep -Milliseconds 50 } } if ($Answer) { Write-Host "`r[" -NoNewline -ForegroundColor DarkGray Write-Host 'Yes' -ForegroundColor DarkGreen -NoNewline Write-Host '/' -NoNewline -ForegroundColor DarkGray Write-Host "N]$(' ' * 40)" -ForegroundColor DarkGray } else { Write-Host "`r[" -NoNewline -ForegroundColor DarkGray Write-Host 'Y/' -NoNewline -ForegroundColor DarkGray Write-Host 'No' -ForegroundColor DarkRed -NoNewline Write-Host "]$(' ' * 40)" -ForegroundColor DarkGray } Return $Answer } function Invoke-AutoHotkey { [CmdletBinding()] [Alias('ahk')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)][String]$Script ) $pipeName = 'AHK_' + [System.Environment]::TickCount $pipeDir = [System.IO.Pipes.PipeDirection]::Out $maxNum = [Int]254 $pipeTMode = [System.IO.Pipes.PipeTransmissionMode]::Message $pipeOptions = [System.IO.Pipes.PipeOptions]::None $ahkPath = [Environment]::GetFolderPath('ProgramFiles') + '\AutoHotkey.AutoHotkey\v2\AutoHotkey64_UIA.exe' $pipe_ga = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName, $pipeDir, $maxNum, $pipeTMode, $pipeOptions) $pipe = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName, $pipeDir, $maxNum, $pipeTMode, $pipeOptions) if ($pipe_ga -and $pipe) { Start-Process $ahkPath "\\.\pipe\$pipeName" $pipe_ga.WaitForConnection() $pipe_ga.Dispose() $pipe.WaitForConnection() $script = [char]65279 + $script $sw = New-Object System.IO.StreamWriter($pipe) $sw.Write($script) $sw.Dispose() $pipe.Dispose() } else { Write-Host 'Operation cancelled: Failed to create named pipe' } } function Start-OBS { [CmdletBinding()] param ( ) if (!(Get-Process 'obs-browser-page' -ErrorAction SilentlyContinue)) { Get-Process 'obs64' -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 2 Start-Process 'C:\Program Files\obs-studio\bin\64bit\obs64.exe' -ArgumentList '--disable-shutdown-check --startrecording' -WorkingDirectory 'C:\Program Files\obs-studio\bin\64bit\' } if (!(Get-Process 'obs64' -ErrorAction SilentlyContinue)) { Start-Process 'C:\Program Files\obs-studio\bin\64bit\obs64.exe' -ArgumentList '--disable-shutdown-check --startrecording' -WorkingDirectory 'C:\Program Files\obs-studio\bin\64bit\' } else { Connect-OBS Start-Sleep -Seconds 2 Start-OBSRecord } } 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, [Alias()][switch]$NoFilter ) $CodeArray = @() if ($Distil) { $Model = "distil-$Model" } if (!$NoFilter) { $FilterString = ' --ff_mdx_kim2' } else { $FilterString = '' } if ($Translate) { $Task = 'translate' $Model = 'large-v2' } else { $Task = 'transcribe' } if ($Folder) { for ($i = 0; $i -lt $Batch; $i++) { $FolderArray = @(Get-FileName -Folder -InitialDirectory '\\aws-server\D\Plex') foreach ($FolderObj in $FolderArray) { $CodeString = "S:\Utilities\Whisper-Faster\faster-whisper-xxl.exe ""$FolderObj"" --language=$Language --model=""$Model""$FilterString --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\faster-whisper-xxl.exe ""$File"" --language=$Language --model=""$Model""$FilterString --beep_off -br --skip --standard --task=$Task" $CodeBlock = [ScriptBlock]::Create($CodeString) $CodeArray += $CodeBlock } } } foreach ($CodeObj in $CodeArray) { Invoke-Command -ScriptBlock $CodeObj } Return } function Connect-awsServer { [Alias('connect-server', 'awss')] [CmdletBinding()] param ( [Parameter(Mandatory = $false)][Alias('c')][pscredential]$Credential ) Begin { if (!$Credential) { $Credential = Get-StoredCredential -Target 'TERMSRV/192.168.0.200' } elseif ($Credential -isnot [pscredential]) { Try { $Credential = Get-Credential -UserName $Credential } Catch { Write-Error $_ } } } Process { $Session = New-PSSession -ComputerName 'aws-server' -Credential $Credential Enter-PSSession -Session $Session } End { } } |