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
        }
    }
}
<#
.SYNOPSIS
    This will display your harvesters lookup times in graphical heatmap.
.DESCRIPTION
    This function will display harvesters lookup times in graphcial heatmap by scaling the seconds to an RGB color and creating a color block with that color.
     
    The number of eligible proofs are placed inside the color block and proof found block will be blue.
     
    A summary is also generated and placed in the title of powershell window. The function will continue to look at the log file and update the heatmap with new harvester activities as they come in.
.EXAMPLE
    PS C:\> Start-ChiaHarvesterWatcher
     
    |1| | | | |2| | | |....
    | | |2|1|3| | | |1|....
 
    This will create the default heat map. With good time showing up as bright green and transitions to yellow then orange then red as the get closer to or above 5 seconds.
.EXAMPLE
    PS C:\> Start-ChiaHarvesterWatcher -DarkMode
 
    |1| | | | |2| | | |....
    | | |2|1|3| | | |1|....
 
    This will create a dark mode version of the heatmap that goes from gray (closer to 0 seconds) to yellow/orange/red as it gets clsoer to 5 seconds.
.EXAMPLE
    PS C:\>Start-ChiaHarvesterWatcher -MaxLookUpSeconds 2 -NoWalls
 
    1 11 2 3 ...
      21 1 3 1 ...
     
    This example will have lookup times close to 0 still be bright green, but the color transitions faster to red since the MaxLookUpSeconds is set to 2.
    Nowall switch takes away the | between color blocks.
.INPUTS
    Inputs (if any)
.OUTPUTS
    Output (if any)
.NOTES
    Requires Chia log levels to be set to INFO.
#>

function Start-ChiaHarvesterWatcher {
    [CmdletBinding()]
    param(
        [string]$DebugLogFilePath = (Get-ChildItem -Path "$ENV:USERPROFILE\.chia\mainnet\log" -filter "debug.log").FullName,
        [ValidateRange(0,1000)]
        [double]$MaxLookUpSeconds = 5,
        [Parameter()]
        [ValidateScript({[System.IO.Directory]::Exists((Split-Path -Path $_ -Parent))})]
        [string]$ExportCSVPath,
        [switch]$DarkMode,
        [switch]$NoWalls
    )

    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 = [int]$Matches[2]
                    LookUpTime = [double]$Matches[5]
                    ProofsFound = [int]$Matches[4]
                    TotalPlots = [int]$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)
                $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 || Last Look up Time: $($harvesterActivity.LookUpTime)"

                $RGB = [math]::Round((255 * $harvesterActivity.LookUpTime) / $MaxLookUpSeconds)
                $RGBText = @{
                    bRed = ([math]::Min($RGB,255))
                    bGreen = ([math]::max([math]::Min(255,(510 - $RGB)),0))
                    UnderLine = $true
                    Text = "$eligibleplots|"
                    NoNewLine = $true
                }

                if ($DarkMode){
                    if ($harvesterActivity.LookUpTime -le $MaxLookUpSeconds){
                            $RGBText["bred"] = [math]::Min(($RGB / 2)+20,255)
                            $RGBText["bgreen"] = [math]::Min(($RGB / 2)+20,255)
                            $RGBText["bblue"] = 20
                    }
                } #Darkmode
                if ($NoWalls){
                    $RGBText["UnderLine"] = $false
                    $RGBText["Text"] = $eligibleplots
                } #NoWalls
                if ($harvesterActivity.ProofsFound){
                    $RGBText["bred"] = 0
                    $RGBText["bgreen"] = 0
                    $RGBText["bblue"] = 255
                } #if proofs found

                Write-RGBText @RGBText
            } #chia activity
        } #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 UniqueId -in $DataHash.MainViewModel.AllVolumes.UniqueId
    foreach ($volume in $CurrentVolumes){
        $matchedVolume = $DataHash.MainViewModel.AllVolumes | where UniqueId -eq $volume.UniqueId
        if ($matchedVolume){
            $matchedVolume.FreeSpace = $volume.FreeSpace
            $matchedVolume = $null
        }
    }

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

    $removedVolumes = $DataHash.MainViewModel.AllVolumes | where UniqueId -NotIn $Volumes.UniqueId
    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