PSChiaPlotter.psm1

enum KSize{
    K32 = 32
    K33 = 33
    K34 = 34
    K35 = 35
}

class MaximizedKSize {
    [KSize]$KSize
    [int]$MaxPlots
    [Decimal]$RemainingBytes
    [Decimal]$KSizeBytes
    [int64]$TotalBytes

    static [Decimal]$K35 = 884.1 * 1gb
    static [Decimal]$K34 = 429.8 * 1gb
    static [Decimal]$K33 = 208.8 * 1gb
    static [Decimal]$K32 = 101.4 * 1gb

    MaximizedKSize(
        [KSize]$KSize,
        [int64]$TotalBytes
    ){
        $this.KSize = $Ksize
        $this.TotalBytes = $TotalBytes

        $this.KSizeBytes = switch ($this.KSize){
            "K35" {[MaximizedKSize]::K35}
            "K34" {[MaximizedKSize]::K34}
            "K33" {[MaximizedKSize]::K33}
            "K32" {[MaximizedKSize]::K32}
        }
        $this.MaxPlots = [math]::Floor([decimal]($this.TotalBytes / $this.KSizeBytes))
        $this.RemainingBytes = $Totalbytes - (([math]::Floor([decimal]($this.TotalBytes / $this.KSizeBytes))) * $this.KSizeBytes)
    }
}
class OptimizedKPlots {
    [int]$K35
    [int]$K34
    [int]$K33
    [int]$K32
    [decimal]$RemainingBytes
    [double]$RemainingGB

    OptimizedKPlots (
        [int]$K35,
        [int]$K34,
        [int]$K33,
        [int64]$Totalbytes
    ){
        $sizeremaining = $TotalBytes - (($K35 * [MaximizedKSize]::K35) + ($K34 * [MaximizedKSize]::K34) + ($K33 * [MaximizedKSize]::K33))
        $k32max = Get-MaxKSize -Totalbytes $sizeremaining -KSize "K32"
        $this.K35 = $K35
        $this.K34 = $K34
        $this.K33 = $K33
        $this.K32 = $k32max.MaxPlots
        $this.RemainingBytes = $k32max.RemainingBytes
        $this.RemainingGB = [math]::Round($k32max.RemainingBytes / 1gb,2)
    }
}
function ConvertTo-FriendlyTimeSpan {
    [CmdletBinding()]
    param(
        [int32]$Seconds
    )

    $TimeSpan = New-TimeSpan -Seconds $Seconds
    switch ($TimeSpan){
        {$_.Days -ge 1} {return "$([math]::Round($TimeSpan.TotalDays,2)) days";break}
        {$_.Hours -ge 1} {return "$([math]::Round($TimeSpan.TotalHours,2)) hrs";break}
        {$_.Minutes -ge 1} {return "$([math]::Round($TimeSpan.TotalMinutes,2)) mins";break}
        {$_.seconds -ge 1} {return "$([math]::Round($TimeSpan.TotalSeconds,2)) sec";break}
    }
}
function Get-ChiaHarvesterActivity {
    [CmdletBinding()]
    param(
        [string[]]$DebugLogFilePath = (Get-ChildItem -Path "$([System.Environment]::GetFolderPath("User"))\.chia\mainnet\log" -filter "debug.log*").FullName,
        [switch]$Summary
    )
    $chiaharvesterlog = "([0-9:.\-T]*) harvester (?:src|chia).harvester.harvester(?:\s?): INFO\s*([0-9]*) plots were eligible for farming ([a-z0-9.]*) Found ([0-9]*) proofs. Time: ([0-9.]*) s. Total ([0-9]*) plots"
    foreach ($logfile in $DebugLogFilePath){
        try{
            $SummaryLog = New-Object 'System.Collections.Generic.List[System.Object]'
            Get-Content -Path $logfile | foreach-object {
                switch -Regex ($_){
                    $chiaharvesterlog {
                        $harvesterActivity = [pscustomobject]@{
                            PSTypeName = "PSChiaPlotter.ChiaHarvesterActivity"
                            Time = [datetime]::parse($Matches[1])
                            EligiblePlots = $Matches[2]
                            LookUpTime = [double]$Matches[5]
                            ProofsFound = $Matches[4]
                            TotalPlots = $Matches[6]
                            FilterRatio = 0
                        } #psobject
                        try { #Prevent the divide by zero error message
                            $harvesterActivity.FilterRatio = $Matches[2] / $Matches[6]
                        } catch { }
                        if (-not$Summary){
                            $harvesterActivity
                        }
                        else{
                            $SummaryLog.Add($harvesterActivity)
                        }
                    }
                } #switch
            } #foreach line
            if ($Summary.IsPresent -and $SummaryLog.Count -ne 0){
                Write-Information "Computing Summary for $logfile"
                if ([System.Environment]::OSVersion.Platform -eq "Win32NT"){
                    $FirstandLast = $SummaryLog | Sort-Object Time -Descending | Select-Object -First 1 -Last 1 | Sort-Object -Descending
                    $RunTime = $FirstandLast[1].Time - $FirstandLast[0].Time
                    if ($RunTime.TotalMinutes -lt 0){$RunTime = $FirstandLast[0].Time - $FirstandLast[1].Time}
                    if ($RunTime.TotalMinutes -ne 0){$ChallengesPerMinute = $SummaryLog.Count / $RunTime.TotalMinutes}
                }
                else{
                    Write-Warning "Unable to calculate average challenges per min on linux due the timestamps missing the date portion."
                }
                [PSCustomObject]@{
                    PSTypeName = "PSChiaPlotter.ChiaHarvesterSummary"
                    RunTime = $RunTime
                    TotalEligiblePlots = ($SummaryLog | Measure-Object EligiblePlots -Sum).Sum
                    BestLookUpTime = ($SummaryLog | Measure-Object LookUpTime -Minimum).Minimum
                    WorstLookUpTime = ($SummaryLog | Measure-Object LookUpTime -Maximum).Maximum
                    AverageLookUpTime = ($SummaryLog | Measure-Object LookUpTime -Average).Average
                    ProofsFound = ($SummaryLog | Measure-Object -Property ProofsFound -Sum).Sum
                    FilterRatio = ($SummaryLog | Measure-Object -Property FilterRatio -Average).Average
                    ChallengesPerMinute = $ChallengesPerMinute
                }
            }
        }
        catch{
            $PSCmdlet.WriteError($_)
        }
    } #foreach
}
function Get-ChiaKPlotCombination{
    [CmdletBinding(DefaultParameterSetName = "DriveLetter")]
    param(
        [Parameter(ParameterSetName="FreeSpace")]
        [int64[]]$FreeSpace,
        [Parameter(ParameterSetName="DriveLetter")]
        [string[]]$DriveLetter = (Get-Volume).DriveLetter
    )

    if ($PSCmdlet.ParameterSetName -eq "FreeSpace"){
        foreach ($space in $FreeSpace){
            $Max = Get-MaxKSize -TotalBytes $space
            $AllCombos = Get-OptimizedKSizePlotNumbers $Max | sort RemainingBytes
            $AllCombos | Add-Member -MemberType NoteProperty -Name "StartingFreeSpace" -Value $space
            $AllCombos
        }
    }
    elseif ($PSCmdlet.ParameterSetName -eq "DriveLetter"){
        foreach ($letter in $DriveLetter){
            $Drive = Get-Volume -DriveLetter $letter
            $Max = Get-MaxKSize -TotalBytes $Drive.SizeRemaining
            $AllCombos = Get-OptimizedKSizePlotNumbers $Max | sort RemainingBytes
            $AllCombos | Add-Member -NotePropertyMembers @{
                DriveLetter = $letter
                FriendlyName = $Drive.FileSystemLabel
            }
            $AllCombos | foreach {$_.psobject.TypeNames.Insert(0,"PSChiaPlotter.KSizeCombination")}
            $AllCombos
        }
    }
}
function Get-ChiaMaxParallelCount {
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateRange(1,128)]
        [int]$ThreadCount = 2,
        [Parameter()]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$BufferMiB = 3390
    )

    if (!$PSBoundParameters.ContainsKey("ThreadCount") -and !$PSBoundParameters.ContainsKey("BufferMiB")){
        Write-Warning "All calculations based on plotting k32 plot size only. SSD TB suggestion rounded up to the nearest TB."
    }
    else{
        Write-Warning "SSD TB suggestion rounded up to the nearest TB."
    }
    $Processor = Get-CimInstance -ClassName Win32_Processor
    $Threads = ($Processor | measure -Property ThreadCount -Sum).Sum
    $MaxParallelCountCPU = [math]::Floor($Threads / $ThreadCount)
    #1mb = 1048576 bytes
    $RAM = (Get-CimInstance -ClassName Win32_PhysicalMemory | measure -Property Capacity -Sum).Sum / 1mb
    $MaxParallelCountRAM = [Math]::Floor([decimal]($RAM / $BufferMiB))

    $SystemDisk = Get-CimInstance -Namespace ROOT/Microsoft/Windows/Storage -ClassName MSFT_Disk -Filter "IsSystem=True"
    $SSDs = Get-CimInstance -Namespace root/microsoft/windows/storage -ClassName MSFT_PhysicalDisk -Filter "MediaType=4" #4 -eq SSD
    $SSDs = $SSDs | where UniqueId -ne $SystemDisk.UniqueId | select 
    $One_TB = 1000000000000
    $One_GB = 1000000000
    $TotalSSDspace = ($SSDs | measure -Property Size -Sum).Sum
    $SSD_Count = ($SSDs | Measure-Object).Count
    if ($SSD_Count -eq 0){
        Write-Warning "No non-system SSD found, therefore Current_MaxParallelPlots will be 0. (Ignore if using mutiple HDDs)"
    }

    if ($Threads -gt ($Processor.NumberOfCores * 2)){
        Write-Warning "Threads may actually only be half what is reported and therefore all calculations are off."
    }

    $SSD_MAX = [math]::Floor([decimal]($TotalSSDspace / (256.6 * $One_GB)))

    if ($MaxParallelCountCPU -le $MaxParallelCountRAM){
        $MAXCount = $MaxParallelCountCPU
        $BottleNeck = "CPU"        
    }
    else{
        $MAXCount = $MaxParallelCountRAM
        $BottleNeck = "RAM"        
    }
    $Suggested_SSD_TB = [math]::Ceiling([decimal](256.6 * $MAXCount) / 1000)

    if ($SSD_MAX -le $MAXCount){
        $CurrentMax = $SSD_MAX
        $BottleNeck = "SSD"
    }
    else{
        $CurrentMax = $MAXCount
    }

    $Suggested_SSD_TB = [math]::Ceiling([decimal](256.6 * $MAXCount) / 1000)

    [PSCustomObject]@{
        ThreadCount = $ThreadCount
        Buffer = $BufferMiB
        CPUTotalThreads = $Threads
        CPUCores = ($Processor | Measure -Property NumberOfCores -Sum).Sum
        NumberOfProcessors = ($Processor | measure).Count
        TotalRAM_MiB = $RAM
        BottleNeck = $BottleNeck
        Current_SSD_SPACE_TB = [math]::Round(($TotalSSDspace / $One_TB),2)
        Current_SSD_Count = $SSD_Count
        Suggested_SSD_SPACE_TB = $Suggested_SSD_TB
        Current_MaxParallelPlots = $CurrentMax
        Potential_MAXParallelPlots = $MAXCount
    }
}
function Get-ChiaPlotProgress {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateScript({Test-Path -Path $_})]
        [string]$LogPath
    )

    if ([System.IO.Directory]::Exists($LogPath)){
        Write-Error "You provided a directory path and not a file path to the log file" -ErrorAction Stop
    }

    #base code from https://github.com/swar/Swar-Chia-Plot-Manager/blob/7287eef4796dbfa4cc009086c6502d19f0706f3e/config.yaml.default
    $phase1_line_end = 801
    $phase2_line_end = 834
    $phase3_line_end = 2474
    $phase4_line_end = 2620
    $copyfile_line_end = 2627
    $phase1_weight = 33
    $phase2_weight = 20
    $phase3_weight = 42
    $phase4_weight = 3
    $copyphase_weight = 2

    $LogItem = Get-Item -Path $LogPath
    $StartTime = $LogItem.CreationTime
    $EndTime = Get-Date
    $ElaspedTime = New-TimeSpan -Start $StartTime -End $EndTime

    $LogFile = Get-Content -Path $LogPath
    $plotId = $LogFile | Select-String -SimpleMatch "ID: " | foreach {$_.ToString().Split(" ")[1]}
    $line_count = $LogFile.Count

    if ($line_count -ge $phase1_line_end){
        $progress += $phase1_weight
    }
    else{
        $progress += $phase1_weight * ($line_count / $phase1_line_end)
        $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $progress
        $secondsRemaining = [int]($Est_TimeRemaining - $ElaspedTime.TotalSeconds)
        return [PSCustomObject]@{
            Progress = [math]::Round($progress,2)
            Phase = "Phase 1"
            ElaspedTime = $ElaspedTime
            EST_TimeReamining = New-TimeSpan -Seconds $secondsRemaining
            PlotId = $plotId
        }
    }
    if ($line_count -ge $phase2_line_end){
        $progress += $phase2_weight
    }
    else{
        $progress += $phase2_weight * (($line_count - $phase1_line_end) / ($phase2_line_end - $phase1_line_end))
        $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $progress
        $secondsRemaining = [int]($Est_TimeRemaining - $ElaspedTime.TotalSeconds)
        return [PSCustomObject]@{
            Progress = [math]::Round($progress,2)
            Phase = "Phase 2"
            ElaspedTime = $ElaspedTime
            EST_TimeReamining = New-TimeSpan -Seconds $secondsRemaining   
            PlotId = $plotId
        }
    }
    if ($line_count -ge $phase3_line_end){
        $progress += $phase3_weight
    }
    else{
        $progress += $phase3_weight * (($line_count - $phase2_line_end) / ($phase3_line_end - $phase2_line_end))
        $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $progress
        $secondsRemaining = [int]($Est_TimeRemaining - $ElaspedTime.TotalSeconds)
        return [PSCustomObject]@{
            Progress = [math]::Round($progress,2)
            Phase = "Phase 3"
            ElaspedTime = $ElaspedTime
            EST_TimeReamining = New-TimeSpan -Seconds $secondsRemaining
            PlotId = $plotId
        }
    }
    if ($line_count -ge $phase4_line_end){
        $progress += $phase4_weight
    }
    else{
        $progress += $phase4_weight * (($line_count - $phase3_line_end) / ($phase4_line_end - $phase3_line_end))
        $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $progress
        $secondsRemaining = [int]($Est_TimeRemaining - $ElaspedTime.TotalSeconds)
        return [PSCustomObject]@{
            Progress = [math]::Round($progress,2)
            Phase = "Phase 4"
            ElaspedTime = $ElaspedTime
            EST_TimeReamining = New-TimeSpan -Seconds $secondsRemaining
            PlotId = $plotId
        }
    }
    if ($line_count -lt $copyfile_line_end){
        $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $progress
        $secondsRemaining = [int]($Est_TimeRemaining - $ElaspedTime.TotalSeconds)
        return [PSCustomObject]@{
            Progress = [math]::Round($progress,2)
            Phase = "Copying"
            ElaspedTime = $ElaspedTime
            EST_TimeReamining = New-TimeSpan -Seconds $secondsRemaining
            PlotId = $plotId
        }
    }
    $progress += $copyphase_weight
    return [PSCustomObject]@{
        Progress = [math]::Round($progress,2)
        Phase = "Completed"
        ElaspedTime = New-TimeSpan -Start $StartTime -End $LogItem.LastWriteTime
        EST_TimeReamining = 0
        PlotId = $plotId
    }
}
function Get-ChiaPlottingStatistic {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [string[]]$Path = (Get-ChildItem -Path $env:USERPROFILE\.chia\mainnet\plotter\ | sort CreationTime -Descending).FullName
    )

    Process{
        foreach ($log in $path){
            if (Test-Path $log){
                $Content = Get-Content -Path $log | Select-String "ID: ","Time for phase","Total time","Plot size","Buffer size","threads of stripe","Copy time","Copied final file from","Starting plotting progress into temporary dirs" | foreach {$_.ToString()}
                foreach ($line in $Content){
                    switch -Wildcard ($line){
                        "ID: *" {$PlotID = $line.Split(' ') | select -Skip 1 -First 1}
                        "Plot size*" {$PlotSize = $line.split(' ') | select -Skip 3} #using select for these since indexing will error if empty
                        "Buffer Size*" {$BufferSize = ($line.Split(' ') | select -Skip 3).split("M") | select -First 1}
                        "*threads*" {$ThreadCount = $line.split(' ') | select -First 1 -Skip 1}
                        "*phase 1*" {$Phase_1 = $line.Split(' ') | select -First 1 -Skip 5}
                        "*phase 2*" {$Phase_2 = $line.Split(' ') | select -First 1 -Skip 5}
                        "*phase 3*" {$Phase_3 = $line.Split(' ') | select -First 1 -Skip 5}
                        "*phase 4*" {$phase_4 = $line.Split(' ') | select -First 1 -Skip 5}
                        "Total time*" {$TotalTime = $line.Split(' ') | select -First 1 -Skip 3}
                        "Copy time*" {$CopyTime = $line.Split(' ') | select -First 1 -Skip 3}
                        "Starting plotting progress into temporary dirs*" {$TempDrive = ($line.Split(' ') | select -First 1 -Skip 6).Split('\') | select -First 1 }
                        "Copied final file from*" {$FinalDrive = ($line.Split(' ') | select -First 1 -Skip 6).Split('\').Replace('"', '') | select -First 1}
                        default {Write-Information "Could not match line: $line"}
                    }
                }
                [PSCustomObject]@{
                    PSTypeName = "PSChiaPlotter.ChiaPlottingStatistic"
                    PlotId = $PlotID
                    KSize = $PlotSize
                    "RAM(MiB)" = $BufferSize
                    Threads = $ThreadCount
                    "Phase_1_sec" = [int]$Phase_1
                    "Phase_2_sec" = [int]$Phase_2
                    "Phase_3_sec" = [int]$Phase_3
                    "Phase_4_sec" = [int]$phase_4
                    "TotalTime_sec" = [int]$TotalTime
                    "CopyTime_sec" = [int]$CopyTime
                    "PlotAndCopyTime_sec" = ([int]$CopyTime + [int]$TotalTime)
                    "Time_Started" = (Get-Item -Path $log).CreationTime 
                    "Temp_drive" = $TempDrive
                    "Final_drive" = $FinalDrive
                }
                Clear-Variable -Name "PlotID","PlotSize","BufferSize","ThreadCount","Phase_1","Phase_2","Phase_3","Phase_4","TotalTime","CopyTime","FinalDrive","TempDrive" -ErrorAction SilentlyContinue
            }
        }
    }
}
function Get-ChiaProcessCounter{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [int[]]$ChiaPID
    )

    foreach ($ID in $ChiaPID){
        $QueryString += "OR IDProcess=$ID "
    }

    $Performance = Get-CimInstance -Query "Select workingSetPrivate,PercentProcessorTime,IDProcess FROM Win32_PerfFormattedData_PerfProc_Process WHERE NAME='_Total' $QueryString"
    $TotalCPU = $Performance | where {$_.Name -eq '_Total'}
    $ChiaProcesses = $Performance | where {$_.Name -ne '_Total'}
    foreach ($process in $ChiaProcesses){
        if ($process.PercentProcessorTime -ne 0){
            $CPUPer = ($process.PercentProcessorTime / $TotalCPU.PercentProcessorTime) * 100
            $RoundedCPU = [math]::Round($CPUPer,2)
        }
        else{$CPUPer = 0}

        [PSCustomObject]@{
            ChiaPID = $process.IDProcess
            CPUPercent = $RoundedCPU
        }
    }
}
function Get-ChiaRAMInfo {
    [CmdletBinding()]
    param(
    )

    Begin{

    } #Begin

    Process{
    
        $Array = Get-CimInstance -Class Win32_PhysicalMemoryArray
        $CurrentRAM = Get-CimInstance -Class Win32_PhysicalMemory

        [PSCustomObject]@{
            PSTypeName = "PSChiaPlotter.RAMInfo"
            ComputerName = $ENV:COMPUTERNAME
            SlotsInUse = ($CurrentRAM | Measure).Count
            SlotsFree = $Array.MemoryDevices - ($CurrentRAM | Measure).Count
            CurrentSize_GB = (($CurrentRAM).Capacity | Measure -Sum).Sum / 1gb
            MaxSize_GB = $Array.MaxCapacityEx / 1mb
            PartNumber = ($CurrentRAM.PartNumber | Select -Unique | foreach {$_.Trim()})
            Manufacturer = ($CurrentRAM.Manufacturer | Select -Unique | foreach {$_.Trim()})
            TotalSlots = $Array.MemoryDevices
            RAMDevices = $CurrentRAM
        }
    } #Process
}
function Show-ChiaPlottingStatistic{
    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [string[]]$LogPath
    )

    $LogStats = Get-ChiaPlottingStatistic -Path $Logpath
    $newlogstats = foreach ($stat in $LogStats){
        try{
            $phase1 = New-TimeSpan -Seconds $stat.Phase_1_sec
            $phase2 = New-TimeSpan -Seconds $stat.Phase_2_sec
            $phase3 = New-TimeSpan -Seconds $stat.Phase_3_sec
            $phase4 = New-TimeSpan -Seconds $stat.Phase_4_sec
            $totaltime = New-TimeSpan -Seconds $stat.TotalTime_sec
            $copyTime = New-TimeSpan -Seconds $stat.CopyTime_sec
            $copyandplot = New-TimeSpan -Seconds $stat.PlotAndCopyTime_sec

            if ($stat.PlotId){
                $stat | Add-Member -NotePropertyMembers @{
                    Phase_1 = $phase1
                    Phase_2 = $phase2
                    Phase_3 = $phase3
                    Phase_4 = $phase4
                    PlotTime = $totaltime
                    CopyPhase = $copyTime
                    PlotAndCopy = $copyandplot
                }
                $stat
            }
        }
        catch{
            Write-Information "Unable to add time span properties"
        }
    }

    if ($logPath.Count -eq 1){
        $WPF = Join-Path -Path $MyInvocation.MyCommand.Module.ModuleBase -ChildPath "WPFWindows"
        $XAMLPath = Join-Path -Path $WPF -ChildPath ChiaLogStats.xaml
        $ChiaLogWindow = Import-Xaml -Path $XAMLPath
        $ChiaLogWindow.DataContext = $newlogstats
        $ChiaLogWindow.ShowDialog() | Out-Null
    }
    else{
        $WPF = Join-Path -Path $MyInvocation.MyCommand.Module.ModuleBase -ChildPath "WPFWindows"
        $XAMLPath = Join-Path -Path $WPF -ChildPath ChiaLogStatsGrid.xaml
        $ChiaLogWindow = Import-Xaml -Path $XAMLPath
        $DataGrid = $ChiaLogWindow.FindName("DataGrid")
        $DataGrid.ItemsSource = $newlogstats
        $ChiaLogWindow.ShowDialog() | Out-Null
    }
}
function Show-PSChiaPlotter {
    [CmdletBinding()]
    param(
        [switch]$DebugWithNotepad
    )
    Add-Type -AssemblyName PresentationFramework

    $PSChiaPlotterFolderPath = "$ENV:LOCALAPPDATA\PSChiaPlotter"
    if (-not(Test-Path -Path $PSChiaPlotterFolderPath)){
        New-Item -Path $PSChiaPlotterFolderPath -ItemType Directory | Out-Null
    }

    $Global:UIHash = [hashtable]::Synchronized(@{})
    $Global:DataHash = [hashtable]::Synchronized(@{})
    $Global:ScriptsHash = [hashtable]::Synchronized(@{})
    $InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
    $UISync = [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new("UIHash", $UIHash, $Null)
    $DataSync = [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new("DataHash", $DataHash, $Null)
    $ScriptsSync = [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new("ScriptsHash", $ScriptsHash, $Null)
    $InitialSessionState.Variables.Add($UISync)
    $InitialSessionState.Variables.Add($DataSync)
    $InitialSessionState.Variables.Add($ScriptsSync)
    $Threads = [int]$ENV:NUMBER_OF_PROCESSORS
    if ($Threads -eq 0){
        Write-Warning "Unable to grab the CPU thread count... please enter the thread count below"
        $Threads = Read-Host -Prompt "How many CPU Threads does this system have?"
        foreach ($char in $Threads.ToCharArray()){
            if (-not[char]::IsNumber($char)){
                Write-Warning "You didn't enter in a number..."
                return
            }
        } #foreach
        if (([int]$Threads -le 0)){
            Write-Warning "You didn't enter in a number above 0... exiting"
            return
        }
    }
    $MaxThreads = ([int]$Threads + 5)
    $RunspacePool = [runspacefactory]::CreateRunspacePool(1,$MaxThreads,$InitialSessionState,$Host)
    $RunspacePool.ApartmentState = "STA"
    $RunspacePool.ThreadOptions = "ReuseThread"
    $RunspacePool.open()

    #DataHash Adding Properties
    $DataHash.ModuleRoot = $MyInvocation.MyCommand.Module.ModuleBase
    $DataHash.PrivateFunctions = Join-Path -Path $DataHash.ModuleRoot -ChildPath "Private"
    $DataHash.LogPath = Join-Path $PSChiaPlotterFolderPath -ChildPath "PSChiaPlotterDebug.log"
    #$DataHash.Assemblies = Join-Path -Path $DataHash.ModuleRoot -ChildPath "Assemblies"
    $DataHash.WPF = Join-Path -Path $DataHash.ModuleRoot -ChildPath "WPFWindows"
    $DataHash.Classes = Join-Path -Path $DataHash.ModuleRoot -ChildPath "Classes"
    $DataHash.Runspaces = New-Object System.Collections.Generic.List[System.Object]
    #DEBUG SWITCH
    $DataHash.Debug = $DebugWithNotepad.IsPresent

    $ScriptsHash.RunspacePool = $RunspacePool

    #Import required assemblies and private functions
    Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName}
    #Get-childItem -Path $DataHash.Assemblies -File | ForEach-Object {Add-Type -Path $_.FullName}

    
    #$QueueRunspace = New-ChiaQueueRunspace
    #$QueueRunspace.Runspacepool = $RunspacePool
    #$ScriptsHash.QueueRunspace = $QueueRunspace

    #Create UI Thread
    $UIRunspace = New-UIRunspace
    $UIRunspace.RunspacePool = $RunspacePool
    $DataHash.UIRunspace = $UIRunspace
    $DataHash.UIHandle = $UIRunspace.BeginInvoke()

    $RunspacePoolEvent = Register-ObjectEvent -InputObject $DataHash.UIRunspace -EventName InvocationStateChanged -Action {
        $NewState = $Event.Sender.InvocationStateInfo.State
        if ($NewState -eq "Completed"){
            try{
                $ScriptsHash.RunspacePool.Close()
                $ScriptsHash.RunspacePool.Dispose()
            }
            catch{
                #write log maybe
            }
        }
        else{
            #do nothing
        }
    }
}
function Start-ChiaHarvesterWatcher {
    [CmdletBinding()]
    param(
        [string]$DebugLogFilePath = (Get-ChildItem -Path "$ENV:USERPROFILE\.chia\mainnet\log" -filter "debug.log").FullName,
        [ValidateRange(1,1000)]
        [int]$Sensitivity = 1,
        [Parameter()]
        [ValidateScript({[System.IO.Directory]::Exists((Split-Path -Path $_ -Parent))})]
        [string]$ExportCSVPath
    )

    if ($PSBoundParameters.ContainsKey("ExportCSVPath")){
        if (-not($ExportCSVPath.EndsWith('.csv'))){
            Write-Warning "Export CSV Path does not end with .csv, please provide a valid CSV path and run the command again... exiting."
            return
        }
    }


    $chiaharvesterlog = "([0-9:.\-T]*) harvester chia.harvester.harvester: INFO\s*([0-9]*) plots were eligible for farming ([a-z0-9.]*) Found ([0-9]*) proofs. Time: ([0-9.]*) s. Total ([0-9]*) plots"
    $BestSpeed = 1000
    $WorstSpeed = 0
    $Over1Seconds = 0
    $Over5Seconds = 0
    $Over30Seconds = 0
    $TotalAttempts = 0
    $TotalFilterRatio = 0
    $TotalLookupTime = 0
    $proofsFound = 0

    Get-Content -Path $DebugLogFilePath -Wait | foreach-object {
        switch -Regex ($_){
            $chiaharvesterlog {
                $harvesterActivity = [pscustomobject]@{
                    Time = [datetime]::parse($Matches[1])
                    EligiblePlots = $Matches[2]
                    LookUpTime = [double]$Matches[5]
                    ProofsFound = $Matches[4]
                    TotalPlots = $Matches[6]
                    FilterRatio = 0
                } #psobject
                try { #Prevent the divide by zero error message
                    $harvesterActivity.FilterRatio = $Matches[2] / $Matches[6]
                } catch { }
                $TotalAttempts++
                switch ($harvesterActivity.LookUpTime) {
                    {$_ -lt $BestSpeed} {$BestSpeed = $_}
                    {$_ -gt $WorstSpeed} {$WorstSpeed = $_}
                    {$_ -ge 1} {$Over1Seconds++}
                    {$_ -ge 5} {$Over5Seconds++}
                    {$_ -ge 30} {$Over30Seconds++}
                }
                if ($PSBoundParameters.ContainsKey("ExportCSVPath")){
                    $harvesterActivity | Export-Csv -Path $ExportCSVPath -Append
                }
                $proofsFound += $harvesterActivity.ProofsFound
                $TotalLookupTime += $harvesterActivity.LookUpTime
                $AverageSpeed = [math]::Round(($TotalLookupTime / $TotalAttempts),5)
                $TotalFilterRatio += $harvesterActivity.FilterRatio
                $newRatio = [math]::Round(($TotalFilterRatio / $TotalAttempts),5)
                $RGB = [math]::Round((255 * $harvesterActivity.LookUpTime * $Sensitivity) / 5)
                $eligibleplots = " "
                if ($harvesterActivity.EligiblePlots -gt 0){
                    $eligibleplots = $harvesterActivity.EligiblePlots
                }
                $host.UI.RawUI.WindowTitle = "Total Attempts: $TotalAttempts || LookUp Time - Best: $BestSpeed, Worst: $WorstSpeed, Avg: $AverageSpeed || Over 1 Sec:$Over1Seconds, Over 5 Sec: $Over5Seconds, Over 30 Sec: $Over30Seconds || FilterRatio: $newRatio || Proofs Found: $proofsFound || RGB: $RGB"
                Write-RGBText -Text "$eligibleplots|" -bRed ([math]::Min($RGB,255)) -bGreen ([math]::max([math]::Min(255,(510 - $RGB)),0)) -NoNewLine -UnderLine
            }
        } #switch
    } #foreach
}
function Start-ChiaParallelPlotting {
    param(
        [ValidateRange(1,128)]
        [int]$ParallelCount = 1,

        [ValidateRange(0,[int]::MaxValue)]
        [Alias("Delay")]
        [int]$DelayInSeconds = 3600,

        [int]$PlotsPerQueue = 1,
        [ValidateRange(3390,[int]::MaxValue)]
        [int]$Buffer = 3390,
        [ValidateRange(1,128)]
        [int]$Threads = 2,

        [Parameter(Mandatory)]
        [ValidateScript({[System.IO.Directory]::Exists($_)})]
        [string]$TempDirectoryPath,
        [Parameter(Mandatory)]
        [ValidateScript({[System.IO.Directory]::Exists($_)})]
        [string]$FinalDirectoryPath,

        [ValidateScript({[System.IO.Directory]::Exists($_)})]
        [string]$LogDirectoryPath = "$ENV:USERPROFILE\.chia\mainnet\plotter",

        [Parameter()]
        [string]$FarmerPublicKey,

        [Parameter()]
        [string]$PoolPublicKey,

        [Parameter()]
        [ValidateRange(1,[int]::MaxValue)]
        [int]$Buckets,

        [Parameter()]
        [ValidateScript({[System.IO.Directory]::Exists($_)})]
        [string]$SecondTempDirectoryPath,

        [switch]$DisableBitfield,
        [switch]$ExcludeFinalDirectory,

        [switch]$NoExit,

        [ValidateNotNullOrEmpty()]
        [string]$WindowTitle
    )

    $AdditionalParameters = ""
    if ($PSBoundParameters.ContainsKey("WindowTitle")){
        $AdditionalParameters += " -WindowTitle $WindowTitle"
    }
    if ($PSBoundParameters.ContainsKey("FarmerPublicKey")){
        $AdditionalParameters += " -FarmerPublicKey $FarmerPublicKey"
    }
    if ($PSBoundParameters.ContainsKey("PoolPublicKey")){
        $AdditionalParameters += " -PoolPublicKey $PoolPublicKey"
    }
    if ($PSBoundParameters.ContainsKey("Buckets")){
        $AdditionalParameters += " -Buckets $Buckets"
    }
    if ($PSBoundParameters.ContainsKey("SecondTempDirectoryPath")){
        $AdditionalParameters += " -SecondTempDirectoryPath '$SecondTempDirectoryPath'"
    }
    if ($DisableBitfield){
        $AdditionalParameters += " -DisableBitfield"
    }
    if ($ExcludeFinalDirectory){
        $AdditionalParameters += " -ExcludeFinalDirectory"
    }

    for ($Queue = 1; $Queue -le $ParallelCount;$Queue++){
        if ($NoExit){
            $NoExitFlag = "-NoExit"
        }
        $ChiaArguments = "-TotalPlots $plotsperQueue -Buffer $Buffer -Threads $Threads -TempDirectoryPath '$TempDirectoryPath' -FinalDirectoryPath '$FinalDirectoryPath' -LogDirectoryPath '$LogDirectoryPath' -QueueName Queue_$Queue $AdditionalParameters"
        $processParam = @{
            FilePath = "powershell.exe"
            ArgumentList = "$NoExitFlag -Command Start-ChiaPlotting $ChiaArguments"
        }
        Start-Process @processParam
        if ($Queue -lt $ParallelCount){
            Start-Sleep -Seconds $DelayInSeconds
        }
    } #for
}
function Start-ChiaPlotting {
    [CmdletBinding()]
    param(
        [ValidateRange(32,35)]
        [int]$KSize = 32,
    
        [ValidateRange(1,5000)]
        [int]$TotalPlots = 1,
    
        [int]$Buffer,

        [ValidateRange(1,256)]
        [int]$Threads = 2,

        [switch]$DisableBitfield,
        [switch]$ExcludeFinalDirectory,
    
        [Parameter(Mandatory)]
        [ValidateScript({[System.IO.Directory]::Exists($_)})]
        [string]$TempDirectoryPath,

        [Parameter()]
        [ValidateScript({[System.IO.Directory]::Exists($_)})]
        [string]$SecondTempDirectoryPath,

        [Parameter(Mandatory)]
        [ValidateScript({[System.IO.Directory]::Exists($_)})]
        [string]$FinalDirectoryPath,

        [Parameter()]
        [string]$FarmerPublicKey,

        [Parameter()]
        [string]$PoolPublicKey,

        [Parameter()]
        [ValidateRange(1,[int]::MaxValue)]
        [int]$Buckets,

        [ValidateScript({[System.IO.Directory]::Exists($_)})]
        [string]$LogDirectoryPath = "$ENV:USERPROFILE\.chia\mainnet\plotter",

        [switch]$NewWindow,

        [string]$QueueName = "Default_Queue",

        [string]$WindowTitle
    )

    if (-not$PSBoundParameters.ContainsKey("Buffer")){
        switch ($KSize){
            32 {$Buffer = 3390}
            33 {$Buffer = 7400}
            34 {$Buffer = 14800}
            35 {$Buffer = 29600}
        }
        Write-Information "Buffer set to: $Buffer"
    }

    if ($PSBoundParameters.ContainsKey("WindowTitle")){
        $WindowTitle = $WindowTitle + " |"
    }

    $E = if ($DisableBitfield){"-e"}
    $X = if ($ExcludeFinalDirectory){"-x"}

    #remove any trailing '\' since chia.exe hates them
    $TempDirectoryPath = $TempDirectoryPath.TrimEnd('\')
    $FinalDirectoryPath = $FinalDirectoryPath.TrimEnd('\')

    #path to chia.exe
    $ChiaPath = (Get-Item -Path "$ENV:LOCALAPPDATA\chia-blockchain\app-*\resources\app.asar.unpacked\daemon\chia.exe").FullName
    $ChiaArguments = "plots create -k $KSize -b $Buffer -r $Threads -t `"$TempDirectoryPath`" -d `"$FinalDirectoryPath`" $E $X"


    if ($PSBoundParameters.ContainsKey("SecondTempDirectoryPath")){
        $SecondTempDirectoryPath = $SecondTempDirectoryPath.TrimEnd('\')
        $ChiaArguments += " -2 $SecondTempDirectoryPath"
        Write-Information "Added 2nd Temp Dir to Chia ArguementList"
    }
    if ($PSBoundParameters.ContainsKey("FarmerPublicKey")){
        $ChiaArguments += " -f $FarmerPublicKey"
    }
    if ($PSBoundParameters.ContainsKey("PoolPublicKey")){
        $ChiaArguments += " -p $PoolPublicKey"
    }

    if ($PSBoundParameters.ContainsKey("Buckets")){
        $ChiaArguments += " -u $Buckets"
    }

    if ($ChiaPath){
        Write-Information "Chia path exists, starting the plotting process"
        if (!$NewWindow){
            for ($plotNumber = 1;$plotNumber -le $TotalPlots;$plotNumber++){
                try{
                    $LogPath = Join-Path $LogDirectoryPath ((Get-Date -Format yyyy_MM_dd_hh-mm-ss-tt_) + "plotlog-" + $plotNumber + ".log")
                    $PlottingParam = @{
                        FilePath = $ChiaPath
                        ArgumentList = $ChiaArguments
                        RedirectStandardOutput = $LogPath
                        NoNewWindow = $true
                    }
                    $chiaProcess = Start-Process @PlottingParam -PassThru
                    $host.ui.RawUI.WindowTitle = "$WindowTitle $QueueName - Plot $plotNumber out of $TotalPlots | Chia Process Id - $($chiaProcess.id)"

                    #Have noticed that giving the process a second to start before checking the logs works better
                    Start-Sleep 1
                
                    while (!$chiaProcess.HasExited){
                        try{
                            $progress = Get-ChiaPlotProgress -LogPath $LogPath -ErrorAction Stop
                            $plotid = $progress.PlotId
                            #write-progress will fail if secondsremaining is less than 0...
                            $secondsRemaining = $progress.EST_TimeReamining.TotalSeconds
                            if ($progress.EST_TimeReamining.TotalSeconds -le 0){
                                $secondsRemaining = 0
                            }
                            Write-Progress -Activity "Queue $($QueueName): Plot $plotNumber out of $TotalPlots" -Status "$($progress.phase) - $($progress.Progress)%" -PercentComplete $progress.progress -SecondsRemaining $secondsRemaining
                            Start-Sleep 5
                        }
                        catch{
                            Write-Progress -Activity "Queue $($QueueName): Plot $plotNumber out of $TotalPlots" -Status "WARNING! PROGRESS UPDATES HAS FAILED! $($progress.phase) - $($progress.Progress)%" -PercentComplete $progress.progress -SecondsRemaining $secondsRemaining
                            Start-Sleep 30
                        }
                    } #while
                    if ($chiaProcess.ExitCode -ne 0){
                        Get-ChildItem -Path $TempDirectoryPath -Filter "*$plotid*.tmp" | Remove-Item -Force
                    }
                }
                catch{
                    $PSCmdlet.WriteError($_)
                }
            } #for
        } #if noNewWindow
        else{
            $ChiaArguments += " -n $TotalPlots"
            $PlottingParam = @{
                FilePath = $ChiaPath
                ArgumentList = $ChiaArguments
                RedirectStandardOutput = $LogPath
            }
            $PlottingProcess = Start-Process @PlottingParam -PassThru
            [PSCustomObject]@{
                KSize = $KSize
                Buffer = $Buffer
                Threads = $Threads
                PID = $PlottingProcess.Id
                StartTime = $PlottingProcess.StartTime
                TempDir = $TempDirectoryPath
                FinalDir = $FinalDirectoryPath
                TempDir2 = $SecondTempDirectoryPath
                LogPath = $LogPath
                TotalPlotCount = $TotalPlots
                BitfieldEnabled = !$DisableBitfield.IsPresent
                ExcludeFinalDir = $ExcludeFinalDirectory.IsPresent
            }
            Write-Information "Plotting started, PID = $PID"
        } # else
    } #if chia path exits
}
function Get-BestChiaFinalDrive {
    [CmdletBinding()]
    param(
        $ChiaVolumes
    )
    $finalplotsize = 101.4 * 1gb

    $AllVolumes = Get-ChiaVolume
    foreach ($finalvol in $ChiaVolumes){
        $newVolumeInfo = $AllVolumes | where UniqueId -eq $finalvol.UniqueId
        $finalvol.FreeSpace = $newVolumeInfo.FreeSpace
        $MasterVolume = $DataHash.MainViewModel.AllVolumes | where UniqueId -eq $finalvol.UniqueId
        $finalvol.PendingFinalRuns = $MasterVolume.PendingFinalRuns
    }
    $sortedVolumes = $ChiaVolumes | Sort-Object -Property @{Expression = {$_.PendingFinalRuns.Count}; Descending = $false},@{Expression = "FreeSpace"; Descending = $True}
    foreach ($volume in $sortedVolumes){
        if (($volume.FreeSpace - ($Volume.PendingFinalRuns.Count * $finalplotsize)) -gt $finalplotsize){
                return $volume
        }
    }
}
function Get-BestChiaTempDrive {
    [CmdletBinding()]
    param(
        $ChiaVolumes,
        $ChiaJob
    )

    $requiredTempSize = 239 * 1gb
    $finalplotsize = 101.4 * 1gb
    $AllVolumes = Get-ChiaVolume
    foreach ($tempvol in $ChiaVolumes){
        $newVolumeInfo = $AllVolumes | where UniqueId -eq $tempvol.UniqueId
        $tempvol.FreeSpace = $newVolumeInfo.FreeSpace
        $MasterVolume = $DataHash.MainViewModel.AllVolumes | where UniqueId -eq $tempvol.UniqueId
        $tempvol.CurrentChiaRuns = $MasterVolume.CurrentChiaRuns
        $tempvol.PendingFinalRuns = $MasterVolume.PendingFinalRuns
    }
    $sortedVolumes = $ChiaVolumes | sort -Property @{Expression = {$_.CurrentChiaRuns.Count}; Descending = $false},@{Expression = "FreeSpace"; Descending = $True}
    foreach ($volume in $sortedVolumes){
        #$MasterVolume = $DataHash.MainViewModel.AllVolumes | where UniqueId -eq $volume.UniqueId
        if (($Volume.CurrentChiaRuns.Count -lt $volume.MaxConCurrentTempChiaRuns) -or ($ChiaJob.IgnoreMaxParallel)){
            if (($volume.FreeSpace - ($Volume.PendingFinalRuns.Count * $finalplotsize)) -gt $requiredTempSize){
                return $volume
            }
        }
    } #foreach
}
function Get-ChiaTempSize{
    [CmdletBinding()]
    param(
        $DirectoryPath,
        $PlotId
    )
    try{
        if ($PlotId -ne $null){
            try{
                #this will actually get the size on disk
                $tepmSize = (Get-ChildItem -Path $DirectoryPath -Filter "*$plotid*.tmp" | foreach {[Disk.Size]::SizeOnDisk($_.FullName)} | measure -Sum).Sum
                return [math]::Round($tepmSize / 1gb)
            }
            catch{
                $tepmSize = (Get-ChildItem -Path $DirectoryPath -Filter "*$plotid*.tmp" | Measure-Object -Property Length -Sum).Sum
                return [math]::Round($tepmSize / 1gb)
            }
        }
        else{
            return 0
        }
    }
    catch{
        return 0
    }
}
function Get-ChiaVolume {
    [CmdletBinding()]
    param()

    #grabbing all volumes, partitions, disks, and physicaldisks at once since it has proven to be faster
    $AllVolumes = Get-Volume
    #filter out all paritions not are not accessible to the file system
    $AllPartitions = Get-Partition | Where {$_.AccessPaths.Count -gt 1}
    $AllDisks = Get-Disk
    $AllphysicalDisk = Get-PhysicalDisk

    foreach ($volume in $AllVolumes){
        try{
            $partition = $AllPartitions | where AccessPaths -Contains "$($volume.UniqueId)"
            $disk = $AllDisks | where DiskNumber -eq $partition.DiskNumber
            $physicalDisk = $AllphysicalDisk | where DeviceId -eq $disk.DiskNumber
            if ($physicalDisk -ne $null){
                $MediaType = $physicalDisk.MediaType
            }
            else{
                $MediaType = "Unknown"
            }

            $Label = $volume.FileSystemLabel
            if ([string]::IsNullOrEmpty($volume.FileSystemLabel)){
                $Label = "N/A"
            }
            $DriveLetter = $volume.DriveLetter
            if (-not[char]::IsLetter($DriveLetter)){
                $DriveLetter = '?'
            }
            if ($partition){
                $DirectoryPaths = $partition.AccessPaths | where {$_ -ne $volume.UniqueId}
                $ChiaVolume = [PSChiaPlotter.ChiaVolume]::new($volume.UniqueId,$Label,$volume.Size,$volume.SizeRemaining)
                $ChiaVolume.BusType = $physicalDisk.BusType
                $ChiaVolume.MediaType = $MediaType
                $MaxTempCount = [math]::Floor([decimal]($volume.size / (239 * 1gb)))
                $ChiaVolume.MaxConCurrentTempChiaRuns = $MaxTempCount
                $ChiaVolume.DriveLetter = $DriveLetter
                $ChiaVolume.DirectoryPath = $DirectoryPaths | select -First 1
                $DirectoryPaths | foreach {$ChiaVolume.AccessPaths.Add($_)}
                $ChiaVolume
                Clear-Variable PhysicalDisk,Disk,Partition,MaxTempCount -ErrorAction SilentlyContinue
            }
        }
        catch{
            #Too dangerous to write to a the same log file at the moment without proper thread safe handling
            #Write-PSChiaPlotterLog -LogType "Error" -LineNumber $_.InvocationInfo.ScriptLineNumber -Message $_.Exception.Message -DebugLogPath $DataHash.LogPath
            #Write-Warning "Unable to create a ChiaVolume from driveletter $($volume.DriveLetter)"
        }
    } #volume

    $mappedDrives = Get-CimInstance -ClassName Win32_MappedLogicalDisk
    $BusType = "Network"
    $MediaType = "Unknown"
    foreach ($drive in $mappedDrives){
        try{
            if ([string]::IsNullOrEmpty($drive.ProviderName)){
                $Label = "N/A"
            }
            else{
                $Label = $drive.ProviderName
            }
            if (-not[string]::IsNullOrEmpty($drive.DeviceID)){
                $DriveLetter = $drive.DeviceID.TrimEnd(':')
                $ChiaVolume = [PSChiaPlotter.ChiaVolume]::new($drive.VolumeSerialNumber,$Label,$drive.Size,$drive.FreeSpace)
                $ChiaVolume.BusType = $BusType
                $ChiaVolume.MediaType = $MediaType
                $MaxTempCount = [math]::Floor([decimal]($drive.size / (239 * 1gb)))
                $ChiaVolume.MaxConCurrentTempChiaRuns = $MaxTempCount
                $ChiaVolume.DriveLetter = $DriveLetter
                $DirectoryPath = $DriveLetter + ':\'
                $ChiaVolume.DirectoryPath = $DirectoryPath
                $ChiaVolume.AccessPaths.Add($DirectoryPath)
                if (Test-Path $label){
                    $ChiaVolume.AccessPaths.Add($Label)
                }
                $ChiaVolume
                Clear-Variable DriveLetter
            }
        }
        catch{
            #Write-PSChiaPlotterLog -LogType "Error" -LineNumber $_.InvocationInfo.ScriptLineNumber -Message $_.Exception.Message -DebugLogPath $DataHash.LogPath
            #Write-Warning "Unable to create a ChiaVolume from driveletter $($DriveLetter.DriveLetter)"
        }
    }
}
function Get-MaxKSize {
    [CmdletBinding()]
    param(
        [ValidateSet("K32","K33","K34","K35")]
        [string[]]$KSize = ("K32","K33","K34","K35"),

        [Parameter(Mandatory)]
        [int64]$TotalBytes
    )

    foreach ($size in $KSize){
        [MaximizedKSize]::new($size,$TotalBytes)
    } #foreach
}
function Get-OptimizedKSizePlotNumbers {
    [CmdletBinding()]
    param(
        [MaximizedKSize[]]$MaximizedKSize
    )

    foreach ($size in $MaximizedKSize){
        switch ($size.KSize){
            "K32" {
                [OptimizedKPlots]::new(0,0,0,$Size.TotalBytes)
            }

            "K33" {
                for ($K33Count = 1; $K33Count -le $size.MaxPlots; $K33Count++){
                    [OptimizedKPlots]::new(0,0,$K33Count,$Size.TotalBytes)
                } #for
            }
            "K34" {
                for ($K34Count = 1; $K34Count -le $size.maxplots; $K34Count++){
                    [OptimizedKPlots]::new(0,$K34Count,0,$Size.TotalBytes)

                    $k34sizeremaining = $Size.TotalBytes - ($K34Count * $size.KSizeBytes)
                    $K33Max = Get-MaxKSize -TotalBytes $k34sizeremaining -KSize "K33"
                    for ($k33 = 1; $k33 -le $k33max.MaxPlots; $k33++){
                        [OptimizedKPlots]::new(0,$K34Count,$k33,$Size.TotalBytes)
                    } #for 33
                } #for 34
            } #34

            "K35" {
                for ($k35count = 1; $k35count -le $size.maxplots; $k35count++){

                    [OptimizedKPlots]::new($k35count,0,0,$Size.TotalBytes)

                    $k35sizeremaining = $Size.TotalBytes - ($k35count * $size.KSizeBytes)
                    $k33max = Get-MaxKSize -Totalbytes $k35sizeremaining -KSize "K33"

                    for ($k33 = 1; $k33 -le $k33max.MaxPlots; $k33++){
                        [OptimizedKPlots]::new($k35count,0,$k33,$Size.TotalBytes)
                    } #for 33

                    $k34max = Get-MaxKSize -Totalbytes $k35sizeremaining -KSize "K34"
                    for ($k34 = 1; $k34 -le $k34max.maxplots; $k34++){
                        [OptimizedKPlots]::new($k35count,$k34,0,$Size.TotalBytes)

                        $sizeremaining = $Size.TotalBytes - (($k35count * $size.KSizeBytes) + ($k34 * $k34max.KSizeBytes))
                        $K33max = Get-MaxKSize -TotalBytes $sizeremaining -KSize "K33"

                        for ($k33 = 1;$k33 -le $k33max.maxplots; $k33++){
                            [OptimizedKPlots]::new($k35count,$k34,$k33,$Size.TotalBytes)
                        }
                    }
                }
            }
        } #switch
    } #foreach
}
function Import-Xaml {
    param(
        $PathToXAML
    )
    Add-Type -AssemblyName PresentationFramework
    Add-Type -AssemblyName System.Windows.Forms
    [xml]$xaml = Get-Content -Path $PathToXAML
    $manager = [System.Xml.XmlNamespaceManager]::new($xaml.NameTable)
    $manager.AddNamespace("x","http://schemas.microsoft.com/winfx/2006/xaml")
    $xamlReader = [System.Xml.XmlNodeReader]::new($xaml)
    [Windows.Markup.XamlReader]::Load($xamlReader)
}
function New-ChiaJobRunspace{
    param(
        [Parameter(Mandatory)]
        $Job
    )
    [powershell]::Create().AddScript{
        Param (
            $Job
        )
        $ErrorActionPreference = "Stop"
        Add-Type -AssemblyName PresentationFramework
        Add-Type -AssemblyName System.Windows.Forms

        #Import required assemblies and private functions
        Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName}
        Get-childItem -Path $DataHash.Classes -File | ForEach-Object {Import-Module $_.FullName}

        for ($queue = 1; $queue -le $Job.QueueCount; $queue++){
            try{
                $newQueue = [PSChiaPlotter.ChiaQueue]::new($queue,$job.InitialChiaParameters,$job)
                $newQueue.Status = "Waiting"
                $DataHash.MainViewModel.AllQueues.Add($newQueue)
                $Job.Queues.Add($newQueue)
            }
            catch{
                Show-Messagebox -Text $_.Exception.Message -Title "Job $($Job.JobNumber) - Runspace"
            }
        }

        try{
            for ($queue = 0;$queue -lt $Job.QueueCount;$queue++){
                if ($queue -eq 0){
                    sleep -Seconds ($Job.FirstDelay * 60)
                    $Job.Status = "Running"
                }

                $Job.Queues[$queue].Status = "Running"
                $QueueRunspace = New-ChiaQueueRunspace -Queue $Job.Queues[$queue] -Job $Job
                $QueueRunspace.Runspacepool = $ScriptsHash.Runspacepool
                [void]$QueueRunspace.BeginInvoke()
                $DataHash.Runspaces.Add($QueueRunspace)
                if (($queue + 1) -ne $Job.QueueCount){
                    #plus 10 seconds for a min delay for data syncing insurance
                    Start-Sleep -Seconds ($Job.DelayInMinutes * 60 + 10)
                }
            }
        }
        catch{
            Show-Messagebox -Text $_.Exception.Message -Title "Job $($Job.JobNumber) - Runspace" | Out-Null
        }
    }.AddParameters($PSBoundParameters)
}
function New-ChiaQueueRunspace {
    param(
        [Parameter(Mandatory)]
        $Queue,
        $Job
    )
    [powershell]::Create().AddScript{
        Param (
            $Job,
            $Queue
        )
        $ErrorActionPreference = "Stop"
        Add-Type -AssemblyName PresentationFramework
        Add-Type -AssemblyName System.Windows.Forms

        #Import required assemblies and private functions
        Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName}
        Get-childItem -Path $DataHash.Classes -File | ForEach-Object {Import-Module $_.FullName}
        try{
            for ($runNumber = 1;($Job.CompletedRunCount + $Job.RunsInProgress.Count) -lt $Job.TotalPlotCount;$runNumber++){
                $ChiaProcess = $Null
                if ($Queue.Pause){
                    $Queue.Status = "Paused"
                    while ($Queue.Pause){
                        sleep 10
                    }
                    if (($Job.CompletedRunCount + $Job.RunsInProgress.Count) -ge $Job.TotalPlotCount){
                        break
                    }
                }

                #grab a volume that has enough space
                Do {
                    Try{
                        $TempVolume = Get-BestChiaTempDrive -ChiaVolumes $Job.TempVolumes -ChiaJob $Job
                        $FinalVolume = Get-BestChiaFinalDrive $Job.FinalVolumes
                        if ($TempVolume -eq $Null){
                            $Queue.Status = "Waiting on Temp Space"
                            Start-Sleep -Seconds 60
                        }
                        elseif ($FinalVolume -eq $Null){
                            $Queue.Status = "Waiting on Final Dir Space"
                            Start-Sleep -Seconds 60
                        }
                    }
                    catch{
                        $Queue.Status = "Failed To Grab Volume Info"
                        Start-Sleep -Seconds 30
                    }
                }
                while ($TempVolume -eq $null -or $FinalVolume -eq $null)
                if (($Job.CompletedRunCount + $Job.RunsInProgress.Count) -ge $Job.TotalPlotCount){
                    break
                }
                $Queue.Status = "Running"
                $plottingParameters = [PSChiaPlotter.ChiaParameters]::New($Queue.PlottingParameters)
                $plottingParameters.TempVolume = $TempVolume
                $plottingParameters.FinalVolume = $FinalVolume
                $newRun = [PSChiaPlotter.ChiaRun]::new($Queue,$runNumber,$plottingParameters)
                
                if ($DataHash.Debug){
                    Start-GUIDebugRun -ChiaRun $newRun -ChiaQueue $Queue -ChiaJob $Job
                }
                else{
                    #Show-Object $newRun
                    Start-GUIChiaPlotting -ChiaRun $newRun -ChiaQueue $Queue -ChiaJob $Job
                }
                #sleep to give some time for updating
                sleep 2
            }
            $Queue.Status = "Finished"
        }
        catch{
            #Write-PSChiaPlotterLog -LogType "Error" -LineNumber $_.InvocationInfo.ScriptLineNumber -Message $_.Exception.Message -DebugLogPath $DataHash.LogPath
            Show-Messagebox -Text $_.Exception.Message -Title "Queue - $($Queue.QueueNumber)" | Out-Null
            if ($ChiaProcess){
                Show-Messagebox -Text "The Following Chia Process may be running and might need to killed - PID $($ChiaProcess.Id)" -Title "Queue" | Out-Null
            }
        }
    }.AddParameters($PSBoundParameters)
}
function New-UIRunspace{
    [powershell]::Create().AddScript{
        $ErrorActionPreference = "Stop"
        Add-Type -AssemblyName PresentationFramework
        Add-Type -AssemblyName System.Windows.Forms
        #[System.Windows.Forms.MessageBox]::Show("Hello")
        #Import required assemblies and private functions
        
        Try{
            Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName}
            Get-childItem -Path $DataHash.Classes -File | ForEach-Object {Import-Module $_.FullName}
            #Get-childItem -Path $DataHash.Assemblies -File | ForEach-Object {Add-Type -Path $_.FullName}
    
            $XAMLPath = Join-Path -Path $DataHash.WPF -ChildPath MainWindow.xaml
            $MainWindow = Import-Xaml -Path $XAMLPath

            #Assign GUI Controls To Variables
            $UIHash.MainWindow = $MainWindow
            $UIHash.Jobs_DataGrid = $MainWindow.FindName("Jobs_DataGrid")
            $UIHash.Queues_DataGrid = $MainWindow.FindName("Queues_DataGrid")
            $UIHash.Runs_DataGrid = $MainWindow.FindName("Runs_DataGrid")
            $UIHash.CompletedRuns_DataGrid = $MainWindow.FindName("CompletedRuns_DataGrid")
            $UIHash.Refreshdrives_Button = $MainWindow.FindName("RefreshdrivesButton")
            $DataHash.RefreshingDrives = $false

            $UIHash.NewJob_Button = $MainWindow.FindName("AddJob_Button")

            $DataHash.MainViewModel = [PSChiaPlotter.MainViewModel]::new()
            $UIHash.MainWindow.DataContext = $DataHash.MainViewModel

            #Add Master Copy of volumes to MainViewModel these are used to keep track of
            # all jobs that are running on the drives
            Get-ChiaVolume | foreach {
                $DataHash.MainViewModel.AllVolumes.Add($_)
            }

            #ButtonClick
            $UIHash.NewJob_Button.add_Click({
                try{
                    #Get-childItem -Path $DataHash.Classes -File | ForEach-Object {Import-Module $_.FullName}
                    $XAMLPath = Join-Path -Path $DataHash.WPF -ChildPath NewJobWindow.xaml
                    $UIHash.NewJob_Window = Import-Xaml -Path $XAMLPath
                    $jobNumber = $DataHash.MainViewModel.AllJobs.Count + 1
                    $newJob = [PSChiaPlotter.ChiaJob]::new()
                    $newJob.JobNumber = $jobNumber
                    $newJob.JobName = "Job $jobNumber"
                    $NewJobViewModel = [PSChiaPlotter.NewJobViewModel]::new($newJob)

                    #need to run get-chiavolume twice or the temp and final drives will be the same object in the application and will update each other...
                    Get-ChiaVolume | foreach {
                        $NewJobViewModel.TempAvailableVolumes.Add($_)
                    }
                    Get-ChiaVolume | foreach {
                        $NewJobViewModel.FinalAvailableVolumes.Add($_)
                    }

                    $newJob.Status = "Waiting"
                    $UIHash.NewJob_Window.DataContext = $NewJobViewModel
                    $CreateJob_Button = $UIHash.NewJob_Window.FindName("CreateJob_Button")
                    $CreateJob_Button.add_Click({
                        try{
                            $Results = Test-ChiaParameters $newJob
                            if ($NewJob.DelayInMinutes -eq 60){
                                $response = Show-Messagebox -Text "You left the default delay time of 60 Minutes, continue?" -Button YesNo
                                if ($response -eq [System.Windows.MessageBoxResult]::No){
                                    return
                                }
                            }
                            if ($Results -ne $true){
                                Show-Messagebox -Text $Results -Title "Invalid Parameters" -Icon Warning
                                return
                            }
                            $DataHash.MainViewModel.AllJobs.Add($newJob)
                            $newJobRunSpace = New-ChiaJobRunspace -Job $newJob
                            $newJobRunSpace.Runspacepool = $ScriptsHash.RunspacePool
                            [void]$newJobRunSpace.BeginInvoke()
                            $DataHash.Runspaces.Add($newJobRunSpace)
                            $UIHash.NewJob_Window.Close()
                        }
                        catch{
                            Show-Messagebox -Text $_.Exception.Message -Title "Create New Job Error" -Icon Error
                        }
                    })

                    $CancelJobCreation_Button = $UIHash.NewJob_Window.FindName("CancelJobCreation_Button")
                    $CancelJobCreation_Button.Add_Click({
                        try{
                            $UIHash.NewJob_Window.Close()
                        }
                        catch{
                            Show-Messagebox -Text $_.Exception.Message -Title "Exit New Job Window Error" -Icon Error
                        }
                    })
    
                    $UIHash.NewJob_Window.ShowDialog()
                }
                catch{
                    Show-Messagebox -Text $_.Exception.Message -Title "Create New Job Error" -Icon Error
                }
            })

            $UIHash.Refreshdrives_Button.Add_Click({
                try{
                    if ($DataHash.RefreshingDrives){
                        Show-Messagebox -Text "A drive refresh is currently in progress" -Icon Information
                        return
                    }
                    $DataHash.RefreshingDrives = $true
                    Update-ChiaVolume -ErrorAction Stop
                    $DataHash.RefreshingDrives = $false
                }
                catch{
                    $DataHash.RefreshingDrives = $false
                    Show-Messagebox -Text $_.Exception.Message -Title "Refresh Drives" -Icon Error
                }
            })

            #$ScriptsHash.QueueHandle = $ScriptsHash.QueueRunspace.BeginInvoke()

            $UIHash.MainWindow.add_Closing({
                Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName}
                # end session and close runspace on window exit
                $DialogResult = Show-Messagebox -Text "Closing this window will end all Chia processes" -Title "Warning!" -Icon Warning -Buttons OKCancel
                if ($DialogResult -eq [System.Windows.MessageBoxResult]::Cancel) {
                    $PSItem.Cancel = $true
                }
                else{
                    #$ScriptsHash.QueueHandle.EndInvoke($QueueHandle)
                    Stop-PSChiaPlotter
                }
            })

            $MainWindow.ShowDialog()


        }
        catch{
            $Message = "$($_.Exception.Message)"
            $Message += "`nLine # -$($_.InvocationInfo.ScriptLineNumber )"
            $Message += "`nLine - $($_.InvocationInfo.Line)"
            Show-Messagebox -Text $Message -Title "UI Runspace Error" -Icon Error
        }
    }
}
function Show-Messagebox {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Text,
        [string]$Title = "Message Box",
        [System.Windows.MessageBoxButton]$Buttons =[System.Windows.MessageBoxButton]::OK,
        [System.Windows.MessageBoxImage]$Icon = [System.Windows.MessageBoxImage]::None
    )

    [System.Windows.MessageBox]::Show($Text,$Title,$Buttons,$Icon)
}
function Start-GUIChiaPlotting {
    [CmdletBinding()]
    param(
        #[string]$SecondTempDirecoryPath,
        #$FarmerPublicKey,
        #$PoolPublicKey,

        $ChiaRun,
        $ChiaQueue,
        $ChiaJob
    )

    #not really needed, but just wanted to make each parameter its own variable
    $PlottingParameters = $ChiaRun.PlottingParameters
    $KSize = $PlottingParameters.KSize
    $Buffer = $PlottingParameters.RAM
    $Threads = $PlottingParameters.Threads
    $DisableBitfield = $PlottingParameters.DisableBitField
    $ExcludeFinalDirectory = $PlottingParameters.ExcludeFinalDirectory
    $TempDirectoryPath = $PlottingParameters.TempVolume.DirectoryPath
    $FinalDirectoryPath = $PlottingParameters.FinalVolume.DirectoryPath
    $LogDirectoryPath = $PlottingParameters.LogDirectory
    #$SecondTempDirecoryPath = $PlottingParameters.TempVolume.DirectoryPath
    $PoolPublicKey = $PlottingParameters.PoolPublicKey
    $FarmerPublicKey = $PlottingParameters.FarmerPublicKey
    $Buckets = $PlottingParameters.Buckets

    $E = if ($DisableBitfield){"-e"}
    $X = if ($ExcludeFinalDirectory){"-x"}

    #remove any trailing '\' since chia.exe hates them
    $TempDirectoryPath = $TempDirectoryPath.TrimEnd('\')
    $FinalDirectoryPath = $FinalDirectoryPath.TrimEnd('\')

    #path to chia.exe
    $ChiaPath = (Get-Item -Path "$ENV:LOCALAPPDATA\chia-blockchain\app-*\resources\app.asar.unpacked\daemon\chia.exe").FullName
    $ChiaArguments = "plots create -k $KSize -b $Buffer -u $Buckets -r $Threads -t `"$TempDirectoryPath`" -d `"$FinalDirectoryPath`" $E $X"

    if (-not[string]::IsNullOrWhiteSpace($PoolPublicKey)){
        $ChiaArguments += " -p $PoolPublicKey"
    }
    if (-not[string]::IsNullOrWhiteSpace($FarmerPublicKey)){
        $ChiaArguments += " -f $FarmerPublicKey"
    }

    if ($ChiaPath){
        Write-Information "Chia path exists, starting the plotting process"
        try{
            $LogPath = Join-Path $LogDirectoryPath ((Get-Date -Format yyyy_MM_dd_hh-mm-ss-tt_) + "plotlog-" + $ChiaQueue.QueueNumber + "-" + $ChiaRun.RunNumber + ".log")
            $ChiaRun.LogPath = $LogPath
            $PlottingParam = @{
                FilePath = $ChiaPath
                ArgumentList = $ChiaArguments
                RedirectStandardOutput = $LogPath
            }
            $chiaProcess = Start-Process @PlottingParam -PassThru -WindowStyle Hidden


            #this is 100% require for th exit code to be seen by powershell when redirectingstandardoutput
            $handle = $chiaProcess.Handle

            $ChiaRun.ChiaProcess = $ChiaProcess
            $ChiaRun.ProcessId = $ChiaProcess.Id
            $ChiaJob.RunsInProgress.Add($ChiaRun)

            $TempMasterVolume = $DataHash.MainViewModel.AllVolumes | where UniqueId -eq $ChiaRun.PlottingParameters.TempVolume.UniqueId
            $TempMasterVolume.CurrentChiaRuns.Add($ChiaRun)
            $FinalMasterVolume = $DataHash.MainViewModel.AllVolumes | where UniqueId -eq $ChiaRun.PlottingParameters.FinalVolume.UniqueId
            $FinalMasterVolume.PendingFinalRuns.Add($ChiaRun)

            $ChiaQueue.CurrentRun = $ChiaRun
            $DataHash.MainViewModel.CurrentRuns.Add($ChiaRun)
            $DataHash.MainViewModel.AllRuns.Add($ChiaRun)

            #Have noticed that giving the process a second to start before checking the logs works better
            Start-Sleep 1
        
            while (!$chiaProcess.HasExited){
                try{
                    $progress = Get-ChiaPlotProgress -LogPath $LogPath -ErrorAction Stop
                    $plotid = $progress.PlotId
                    $ChiaRun.Progress = $progress.progress
                    $ChiaQueue.CurrentTime = [DateTime]::Now
                    $ChiaRun.CurrentTime = [DateTime]::Now
                    $ChiaRun.Phase = $progress.Phase
                    if ($progress.EST_TimeReamining.TotalSeconds -le 0){
                        $ChiaRun.EstTimeRemaining = New-TimeSpan -Seconds 0
                    }
                    else{
                        $ChiaRun.EstTimeRemaining = $progress.EST_TimeReamining
                    }
                    $ChiaRun.EstTimeRemaining = $progress.EST_TimeReamining
                    $ChiaRun.TempSize = Get-ChiaTempSize -DirectoryPath $TempDirectoryPath -PlotId $plotid
                    Start-Sleep (5 + $ChiaQueue.QueueNumber)
                }
                catch{
                    Start-Sleep 30
                }
            } #while

            $ChiaJob.RunsInProgress.Remove($ChiaRun)
            $ChiaJob.CompletedRunCount++
            $FinalMasterVolume.PendingFinalRuns.Remove($ChiaRun)
            $TempMasterVolume.CurrentChiaRuns.Remove($ChiaRun)
            $ChiaRun.ExitCode = $ChiaRun.ChiaPRocess.ExitCode
            #if this is null then an error will occur if we try to set this property
            if ($ChiaRun.ExitTime){
                $ChiaRun.ExitTime = $ChiaProcess.ExitTime
            }

            if ($ChiaRun.ChiaPRocess.ExitCode -ne 0){
                $ChiaRun.Status = "Failed"
                $ChiaQueue.FailedPlotCount++
                $ChiaJob.FailedPlotCount++
                $DataHash.MainViewModel.FailedRuns.Add($ChiaRun)
                Get-ChildItem -Path $TempDirectoryPath -Filter "*$plotid*.tmp" | foreach {
                    try{
                        Remove-Item -Path $_.FullName -Force -ErrorAction Stop
                    }
                    catch{
                        Show-Messagebox -Text $_.Exception.Message | Out-Null
                    }
                }
            }
            else{
                $ChiaRun.Status = "Completed"
                $ChiaJob.CompletedPlotCount++
                $ChiaQueue.CompletedPlotCount++
                $DataHash.MainViewModel.CompletedRuns.Add($ChiaRun)
                Update-ChiaGUISummary -Success
            }
            $DataHash.MainViewModel.CurrentRuns.Remove($ChiaRun)
        }
        catch{
            if (-not$DataHash.MainViewModel.FailedRuns.Contains($ChiaRun)){
                $DataHash.MainViewModel.FailedRuns.Add($ChiaRun)
            }
            if ($DataHash.MainViewModel.CurrentRuns.Contains($ChiaRun)){
                $DataHash.MainViewModel.CurrentRuns.Remove($ChiaRun)
            }
            if ($ChiaJob.RunsInProgress.Contains($ChiaRun)){
                $ChiaJob.RunsInProgress.Remove($ChiaRun)
            }
            if ($FinalMasterVolume){
                if ($FinalMasterVolume.PendingFinalRuns.Contains($ChiaRun)){
                    $FinalMasterVolume.PendingFinalRuns.Remove($ChiaRun)
                }
            }
            $PSCmdlet.WriteError($_)
        }
    } #if chia path exits
    else{
        $Message = "chia.exe was not found"
        $ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
                [System.IO.FileNotFoundException]::new($Message,"$ENV:LOCALAPPDATA\chia-blockchain\app-*\resources\app.asar.unpacked\daemon\chia.exe"),
                'ChiaPathInvalid',
                [System.Management.Automation.ErrorCategory]::ObjectNotFound,
                "$ENV:LOCALAPPDATA\chia-blockchain\app-*\resources\app.asar.unpacked\daemon\chia.exe"
            )
            $PSCmdlet.ThrowTerminatingError($ErrorRecord)
        $PSCmdlet.ThrowTerminatingError("Invalid Log Path Directory: $LogDirectoryPath")
    }
}
function Start-GUIDebugRun{
    [CmdletBinding()]
    param(
        $ChiaRun,
        $ChiaQueue,
        $ChiaJob
    )
    try{
        $PlottingParameters = $ChiaRun.PlottingParameters
        $KSize = $PlottingParameters.KSize
        $Buffer = $PlottingParameters.RAM
        $Threads = $PlottingParameters.Threads
        $DisableBitfield = $PlottingParameters.DisableBitField
        $ExcludeFinalDirectory = $PlottingParameters.ExcludeFinalDirectory
        $TempDirectoryPath = $PlottingParameters.TempVolume.DirectoryPath
        $FinalDirectoryPath = $PlottingParameters.FinalVolume.DirectoryPath
        $LogDirectoryPath = $PlottingParameters.LogDirectory
        $SecondTempDirecoryPath = $PlottingParameters.TempVolume.DirectoryPath
    
        $E = if ($DisableBitfield){"-e"}
        $X = if ($ExcludeFinalDirectory){"-x"}
    
        if (Test-Path $LogDirectoryPath){
            $LogPath = Join-Path $LogDirectoryPath ((Get-Date -Format yyyy_MM_dd_hh-mm-ss-tt_) + "plotlog" + ".log")
        }
        $ChiaProcess = start-process -filepath notepad.exe -PassThru -RedirectStandardOutput $LogPath
        $handle = $ChiaProcess.Handle
        $ChiaRun.ChiaProcess = $ChiaProcess
        $ChiaRun.ProcessId = $ChiaProcess.Id
        $DataHash.MainViewModel.AllRuns.Add($ChiaRun)
        $ChiaJob.RunsInProgress.Add($ChiaRun)
        
        $TempMasterVolume = $DataHash.MainViewModel.AllVolumes | where UniqueId -eq $ChiaRun.PlottingParameters.TempVolume.UniqueId
        $TempMasterVolume.CurrentChiaRuns.Add($ChiaRun)
        $FinalMasterVolume = $DataHash.MainViewModel.AllVolumes | where UniqueId -eq $ChiaRun.PlottingParameters.FinalVolume.UniqueId
        $FinalMasterVolume.PendingFinalRuns.Add($ChiaRun)
    
        $ChiaQueue.CurrentRun = $ChiaRun
        $DataHash.MainViewModel.CurrentRuns.Add($ChiaRun)
        while (-not$ChiaProcess.HasExited){
            $ChiaQueue.CurrentTime = [DateTime]::Now
            $ChiaRun.CurrentTime = [DateTime]::Now
            $ChiaRun.Progress += 5
            sleep (10 + $ChiaQueue.QueueNumber)
        }

        $TempMasterVolume.CurrentChiaRuns.Remove($ChiaRun)
        $FinalMasterVolume.PendingFinalRuns.Remove($ChiaRun)
        $ChiaJob.RunsInProgress.Remove($ChiaRun)
        $ChiaJob.CompletedRunCount++

        $ChiaRun.ExitCode = $ChiaProcess.ExitCode
        if ($ChiaProcess.ExitTime -ne $null){
            $ChiaRun.ExitTime = $ChiaProcess.ExitTime
        }
        $ChiaRun.ExitTime = $ChiaProcess.ExitTime
        if ($ChiaProcess.ExitCode -eq 0){
            $ChiaRun.Status = "Completed"
            $ChiaJob.CompletedPlotCount++
            $ChiaQueue.CompletedPlotCount++
            $DataHash.MainViewModel.CompletedRuns.Add($ChiaRun)
            Update-ChiaGUISummary -Success
        }
        else{
            $ChiaRun.Status = "Failed"
            $ChiaJob.FailedPlotCount++
            $ChiaQueue.FailedPlotCount++
            $DataHash.MainViewModel.FailedRuns.Add($ChiaRun)
        }
        $DataHash.MainViewModel.CurrentRuns.Remove($ChiaRun)
    }
    catch{
        if (-not$DataHash.MainViewModel.FailedRuns.Contains($ChiaRun)){
            $DataHash.MainViewModel.FailedRuns.Add($ChiaRun)
        }
        if ($DataHash.MainViewModel.CurrentRuns.Contains($ChiaRun)){
            $DataHash.MainViewModel.CurrentRuns.Remove($ChiaRun)
        }
        if ($ChiaJob.RunsInProgress.Contains($ChiaRun)){
            $ChiaJob.RunsInProgress.Remove($ChiaRun)
        }
        if ($FinalMasterVolume){
            if ($FinalMasterVolume.PendingFinalRuns.Contains($ChiaRun)){
                $FinalMasterVolume.PendingFinalRuns.Remove($ChiaRun)
            }
        }
        $PSCmdlet.WriteError($_)
    }
}
function Stop-PSChiaPlotter{
    [CmdletBinding()]
    param()

    $RunningQueues = $DataHash.MainViewModel.AllQueues | Where-Object Status -eq "Running"
    foreach ($Queue in $RunningQueues){
        $queue.Status = "Paused"
    }

    $ALLChiaProcesses = $DataHash.MainViewModel.CurrentRuns
    foreach ($run in $ALLChiaProcesses){
        try{
            Stop-Process $run.ProcessID
        }
        catch{
            $logParam = @{
                LogType = "Error"
                Message = $_.Exception.Message
                LineNumber = $_.InvocationInfo.ScriptLineNumber
                DebugLogPath = $DataHash.LogPath
            }
            Write-PSChiaPlotterLog @logParam
        }
    }
    $RunningRunspaces = $DataHash.Runspaces
    foreach ($runspace in $RunningRunspaces){
        try{
            $runspace.Stop()
        }
        catch{
            $logParam = @{
                LogType = "Error"
                Message = $_.Exception.Message
                LineNumber = $_.InvocationInfo.ScriptLineNumber
                DebugLogPath = $DataHash.LogPath
            }
            Write-PSChiaPlotterLog @logParam
        }
    }
}
function Test-ChiaParameters {
    param(
        $NewJob
    )
    $ChiaParameters = $NewJob.InitialChiaParameters

    if ([string]::IsNullOrEmpty($NewJob.JobName)){
        return "Job Name cannot be null or empty"
    }
    if ($ChiaParameters.RAM -lt 1000){
        return "RAM needs to be greater than 1000"
    }
    if ($ChiaParameters.Threads -le 0){
        return "Threads needs to 1 or higher"
    }
    if ($ChiaParameters.Buckets -le 0){
        return "Buckets cannot be less than 1"
    }
    if ($NewJob.TempVolumes.Count -lt 1){
        return "No Temp drives have been added!"
    }
    foreach ($tempvol in $NewJob.TempVolumes){
        if (-not[System.IO.Directory]::Exists($tempvol.DirectoryPath)){
            return "Temp Directory `"$($tempvol.DirectoryPath)`" does not exists"
        }
        $ValidPath = $false
        foreach ($path in $tempvol.AccessPaths){
            if ($tempvol.DirectoryPath.StartsWith($path)){
                $ValidPath = $true
            }
        } #foreach
        if (-not$ValidPath){
            return "Directory path '$($tempvol.DirectoryPath)' for Drive $($tempvol.DriveLetter) does not start with a valid access path, valid paths shown below.`n`n$($tempvol.AccessPaths | foreach {"$_`n"})"
        }
    }
    if ($NewJob.FinalVolumes.Count -lt 1){
        return "No Final Drives have been added!"
    }
    foreach ($finalvol in $NewJob.FinalVolumes){
        if (-not[System.IO.Directory]::Exists($finalvol.DirectoryPath)){
            return "Final Directory `"$($finalvol.DirectoryPath)`" does not exists"
        }
        $ValidPath = $false
        foreach ($path in $finalvol.AccessPaths){
            if ($finalvol.DirectoryPath.StartsWith($path)){
                $ValidPath = $true
            }
        } #foreach
        if (-not$ValidPath){
            return "Directory path '$($finalvol.DirectoryPath)' for Drive $($finalvol.DriveLetter) does not start with a valid access path, valid paths shown below.`n`n$($finalvol.AccessPaths | foreach {"$_`n"})"
        }
    }
    if (-not[System.IO.Directory]::Exists($ChiaParameters.LogDirectory)){
        return "Log Directory does not exists"
    }
    if ($NewJob.DelayInMinutes -gt 35791){
        return "Delay Time is greater than 35791 minutes, which is the max"
    }
    if ($NewJob.FirstDelay -gt 35791){
        return "First delay time is greater than 35791 minutes, which is the max"
    }
    return $true
}
function Update-ChiaGUISummary{
    [CmdletBinding()]
    param(
        [switch]$Success,
        [switch]$Failed
    )

    if ($Success){
        $OneDayAgo = (Get-Date).AddDays(-1)
        $PlotsIn24Hrs = ($DataHash.MainViewModel.CompletedRuns | where ExitTime -GT $OneDayAgo | Measure-Object).Count
        $DataHash.MainViewModel.PlotPlottedPerDay = $PlotsIn24Hrs
        $DataHash.MainViewModel.TBPlottedPerDay = [math]::Round(($PlotsIn24Hrs * 101.4) / 1000,2)

        $SortedRuns = $DataHash.MainViewModel.CompletedRuns | Sort-Object -Property Runtime
        $Fastest = $SortedRuns | Select-Object -First 1
        $Slowest = $SortedRuns | Select-Object -Last 1
        $Average = $SortedRuns.RunTime | Measure-Object -Property TotalSeconds -Average

        if ($Fastest){
            $DataHash.MainViewModel.FastestRun = $Fastest.Runtime
        }
        if ($Slowest){
            $DataHash.MainViewModel.SlowestRun = $Slowest.Runtime
        }
        if ($Average){
            $AverageRun = New-TimeSpan -Seconds $Average.Average
            $DataHash.MainViewModel.AverageTime = $AverageRun
        }
    }
}
function Update-ChiaVolume {
    [CmdletBinding()]
    param()
    $Volumes = Get-ChiaVolume
    $CurrentVolumes = $Volumes | where DriveLetter -in $DataHash.MainViewModel.AllVolumes.DriveLetter
    foreach ($volume in $CurrentVolumes){
        $matchedVolume = $DataHash.MainViewModel.AllVolumes | where DriveLetter -eq $volume.DriveLetter
        if ($matchedVolume){
            $matchedVolume.FreeSpace = $volume.FreeSpace
            $matchedVolume = $null
        }
    }

    $newVolumes = $Volumes | where DriveLetter -notin $DataHash.MainViewModel.AllVolumes.DriveLetter
    foreach ($newvolume in $newVolumes){
        $DataHash.MainViewModel.AllVolumes.Add($newvolume)
    }

    $removedVolumes = $DataHash.MainViewModel.AllVolumes | where DriveLetter -NotIn $Volumes.DriveLetter
    foreach ($removedvolume in $removedVolumes){
        $DataHash.MainViewModel.AllVolumes.Remove($removedvolume)
    }
}
function Write-PSChiaPlotterLog {
    [CmdletBinding()]
    param(
        [ValidateSet("INFO","Warning","ERROR")]
        [string]$LogType,
        [string]$Message,
        [int]$LineNumber,
        [string]$DebugLogPath
    )

    try{
        $Date = Get-Date -Format "[yyyy-MM-dd.HH:mm:ss]"
        $LogLine = "$Date-$LogType-$LineNumber-$Message"
        $LogLine | Out-File $DebugLogPath -Append
    }
    catch{
        $PSCmdlet.WriteError($_)
    }
}
function Write-RGBText {
    [CmdletBinding()]
    param(
        [string]$Text,

        [Parameter(Position = 1)]
        [int]$fRed = 0,
        [int]$fGreen = 0,
        [int]$fBlue = 0,

        [int]$bRed = 0,
        [int]$bGreen = 0,
        [int]$bBlue = 0,
        
        # No newline after the text.
        [Parameter()]
        [switch] $NoNewLine,

        [switch]$UnderLine
    )

    $escape = [char]27 + '['
    $resetAttributes = "$($escape)0m"

    if ($UnderLine){
        $UL = "$($escape)4m"
    }
    
    $foreground = "$($escape)38;2;$($fRed);$($fGreen);$($fBlue)m"
    $background = "$($escape)48;2;$($bRed);$($bGreen);$($bBlue)m"
    
    Write-Host ($foreground + $background + $UL + $Text + $resetAttributes) -NoNewline:$NoNewLine
}
Export-ModuleMember -function ConvertTo-FriendlyTimeSpan, Get-ChiaHarvesterActivity, Get-ChiaKPlotCombination, Get-ChiaMaxParallelCount, Get-ChiaPlotProgress, Get-ChiaPlottingStatistic, Get-ChiaProcessCounter, Get-ChiaRAMInfo, Show-ChiaPlottingStatistic, Show-PSChiaPlotter, Start-ChiaHarvesterWatcher, Start-ChiaParallelPlotting, Start-ChiaPlotting