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

    $plotProgressObject = [PSCustomObject]@{
        Progress = 0
        Phase = "Phase 1"
        ElaspedTime = $ElaspedTime
        EST_TimeRemaining = New-TimeSpan
        PlotId = $plotId
        Phase1Progess = 0
        Phase2Progess = 0
        Phase3Progess = 0
        Phase4Progess = 0
        CopyProgess = 0
    }

    if ($line_count -ge $phase1_line_end){
        $plotProgressObject.Phase1Progess = 100
        $plotProgressObject.Progress += $phase1_weight
    }
    else{
        $Phase1Progess = ($line_count / $phase1_line_end)
        $plotProgressObject.Phase1Progess = [math]::Round(($Phase1Progess * 100),2)
        $plotProgressObject.Progress += $phase1_weight * $Phase1Progess
        $plotProgressObject.Phase = "Phase 1"
        $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $plotProgressObject.Progress
        $plotProgressObject.EST_TimeRemaining = New-TimeSpan -Seconds ($Est_TimeRemaining - $ElaspedTime.TotalSeconds)

        return $plotProgressObject
    }
    if ($line_count -ge $phase2_line_end){
        $plotProgressObject.Phase2Progess = 100
        $plotProgressObject.Progress += $phase2_weight
    }
    else{
        $phase2Progress = ($line_count - $phase1_line_end) / ($phase2_line_end - $phase1_line_end)
        $plotProgressObject.Phase2Progess = [math]::Round(($phase2Progress * 100),2)
        $plotProgressObject.Progress += $phase2_weight * $phase2Progress
        $plotProgressObject.Phase = "Phase 2"

        $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $plotProgressObject.Progress
        $plotProgressObject.EST_TimeRemaining = New-TimeSpan -Seconds ($Est_TimeRemaining - $ElaspedTime.TotalSeconds)

        return $plotProgressObject
    }
    if ($line_count -ge $phase3_line_end){
        $plotProgressObject.Phase3Progess = 100
        $plotProgressObject.Progress += $phase3_weight
    }
    else{
        $phase3Progess = ($line_count - $phase2_line_end) / ($phase3_line_end - $phase2_line_end)
        $plotProgressObject.Phase3Progess = [math]::Round(($phase3Progess * 100),2)
        $plotProgressObject.Progress += $phase3_weight * $phase3Progess
        $plotProgressObject.Phase = "Phase 3"

        $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $plotProgressObject.Progress
        $plotProgressObject.EST_TimeRemaining = New-TimeSpan -Seconds ($Est_TimeRemaining - $ElaspedTime.TotalSeconds)
        return $plotProgressObject
    }
    if ($line_count -ge $phase4_line_end){
        $plotProgressObject.Phase4Progess = 100
        $plotProgressObject.Progress += $phase4_weight
    }
    else{
        $phase4Progess = ($line_count - $phase3_line_end) / ($phase4_line_end - $phase3_line_end)
        $plotProgressObject.Phase4Progess = [math]::Round(($phase4Progess * 100),2)
        $plotProgressObject.Progress += $phase4_weight * $phase4Progess
        $plotProgressObject.Phase = "Phase 4"
        
        $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $plotProgressObject.Progress
        $plotProgressObject.EST_TimeRemaining = New-TimeSpan -Seconds ($Est_TimeRemaining - $ElaspedTime.TotalSeconds)

        return $plotProgressObject
    }
    if ($line_count -lt $copyfile_line_end){
        $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $plotProgressObject.Progress
        $plotProgressObject.EST_TimeRemaining = New-TimeSpan -Seconds ($Est_TimeRemaining - $ElaspedTime.TotalSeconds)
        $plotProgressObject.Phase = "Copying"

        return $plotProgressObject
    }
    $plotProgressObject.Progress += $copyphase_weight
    $plotProgressObject.CopyProgess = 100
    $plotProgressObject.Phase = "Complete"
    $plotProgressObject.ElaspedTime = New-TimeSpan -Start $StartTime -End $LogItem.LastWriteTime
    $plotProgressObject.EST_TimeRemaining = 0
    return $plotProgressObject
}
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,
        [Parameter(DontShow)]
        [switch]$NoNewWindow,
        [Parameter(DontShow)]
        [int]$Threads = [int]$ENV:NUMBER_OF_PROCESSORS
    )
    Add-Type -AssemblyName PresentationFramework

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

    if (-not$PSBoundParameters.ContainsKey("Threads")){
        $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"
            $Respnose = Read-Host -Prompt "How many CPU Threads does this system have?"
            foreach ($char in $Respnose.ToCharArray()){
                if (-not[char]::IsNumber($char)){
                    Write-Warning "You didn't enter in a number..."
                    return
                }
            } #foreach
            $Threads = [int]$Respnose
            if (([int]$Threads -le 0)){
                Write-Warning "You didn't enter in a number above 0... exiting"
                return
            }
        }
    }

    $PSChiaPlotterFolderPath = Join-Path -Path $ENV:LOCALAPPDATA -ChildPath 'PSChiaPlotter\Logs'
    $LogName = (Get-Date -Format yyyyMMdd) + '_debug.log'
    $LogPath = Join-Path -Path $PSChiaPlotterFolderPath -ChildPath $LogName
    if (-not$NoNewWindow.IsPresent){
        $parameters = @{
            FilePath = "powershell.exe"
            ArgumentList = "-NoExit -NoProfile -STA -Command Show-PSChiaPlotter -NoNewWindow -Threads $Threads"
            WindowStyle = "Hidden"
            RedirectStandardOutput = $LogPath
        }
        Start-Process @parameters
        return
    }
    #Start-Transcript -Path $LogPath | 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)

    $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.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
    $DataHash.LogPath = $LogPath

    $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()
    $Date = Get-Date -Format "[yyyy-MM-dd.HH:mm:ss]"
    Write-Host "[INFO]-$Date-Staring PSChiaPlotter GUI - PID = $($PID)"

    $UIHash.NewWindow = $true
    $UIHash.PowershellPID = $PID

    $RunspacePoolEvent = Register-ObjectEvent -InputObject $DataHash.UIRunspace -EventName InvocationStateChanged -Action {
        $NewState = $Event.Sender.InvocationStateInfo.State
        #Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName}
        #for some reason importing the private functions is not working...
        $Date = Get-Date -Format "[yyyy-MM-dd.HH:mm:ss]"
        if ($NewState -eq "Completed"){
            try{
                $ScriptsHash.RunspacePool.Close()
                $ScriptsHash.RunspacePool.Dispose()
                if ($UIHash.NewWindow){
                    
                    $Message = "PSChiaPlotter has been closed... Stopping Powershell process with PID of $($UIHash.PowershellPID)"
                    Write-Host "[INFO]-$Date-$Message"
                    Stop-Process -Id $UIHash.PowershellPID -Force
                }
            }
            catch{
                $Date = Get-Date -Format "[yyyy-MM-dd.HH:mm:ss]"
                $Message = "PSChiaPlotter has been closed... unable to stop powershell process with PID of $($UIHash.PowershellPID)"
                Write-Host "[WARNING]-$Date-$WARNING-$Message"
                Write-Host "[ERROR]-$Date-$($_.InvocationInfo.ScriptLineNumber)-$($_.Exception.Message)"
            }
        }
        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_TimeRemaining.TotalSeconds
                            if ($progress.EST_TimeRemaining.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 Test-ChiaPlot {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipelineByPropertyName,ValueFromPipeline)]
        [Alias("FilePath","FullName","Filter")]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path,
        [ALias("n")]
        [int]$Challenges = 30
    )

    Begin{
        $ChiaPath = (Get-Item -Path "$ENV:LOCALAPPDATA\chia-blockchain\app-*\resources\app.asar.unpacked\daemon").FullName
        if ($ENV:Path.Split(";") -notcontains $ChiaPath){
            $ENV:Path += ";$ChiaPath"
        }
        $Proofs = "Proofs ([0-9]*) / ([0-9]*), ([0-9.]*)"
        $Testing = "Testing plot"
        $ErrorString = "ERROR"
    }

    Process{
        foreach ($plotpath in $Path){
            chia.exe plots check -n $Challenges -g $plotpath 2>&1 | Select-String -SimpleMatch "Proofs","Error","Testing" | foreach {
                switch -Regex ($_){
                    $Proofs {
                        $PlotObject.ProofsFound = $Matches[1] -as [int]
                        $PlotObject.Ratio = $Matches[3] -as [double]
                        $PlotObject
                        break
                    }
                    $Testing {
                        $PlotPath = ($_ -split "Testing plot ")[1].split(' ')[0]
                        $Leaf = Split-Path -Path $PlotPath -Leaf
                        $KSize = $Leaf -split "plot-k" -split "-" | Select-Object -First 1 -Skip 1
                        $PlotId = ($Leaf -split "-" | select -Last 1).Split(".")[0]
                        $PlotObject = [PSCustomObject]@{
                            PSTypeName = "PSChiaPlotter.PlotTest"
                            Path = $PlotPath
                            ProofsFound = 0
                            Challenges = $Challenges
                            Ratio = 0.0
                            KSize = $KSize
                            PlotId = $PlotId
                            Errors = New-Object System.Collections.Generic.List[string]
                        }
                        break
                    }
                    $ErrorString {
                        $PlotObject.Errors.Add($_.ToString())
                        break
                    }
                } #switch
            } #foreach line
        } #foreach
    } #process
}
function Test-ChiaPlotParallel {
    [CmdletBinding()]
    param(
        [Parameter()]
        [Alias("FilePath","FullName")]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path,
        [ValidateRange(1,256)]
        [int]$Threads =  1,
        [ALias("n")]
        [int]$Challenges = 30
    )

    Begin{
        $ChiaPath = (Get-Item -Path "$ENV:LOCALAPPDATA\chia-blockchain\app-*\resources\app.asar.unpacked\daemon").FullName
        if ($ENV:Path.Split(";") -notcontains $ChiaPath){
            $ENV:Path += ";$ChiaPath"
        }
        $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
        $RunspacePool = [runspacefactory]::CreateRunspacePool(1,$Threads,$SessionState,$Host)
        $RunspacePool.ApartmentState = "STA"
        $RunspacePool.ThreadOptions = "ReuseThread"
        $RunspacePool.Open()
        $Jobs = New-Object System.Collections.Generic.List[Object]
        $TotalPlots = $Path.Count
    } #Begin

    Process{
        foreach ($plotpath in $Path){
            Write-Information "Adding runspace for $plot"
            $Parameters = @{
                PlotPath = $plotpath
                Challenges = $Challenges
                ChiaPath = $ChiaPath
            }
            $CheckPlotScript = [powershell]::Create().AddScript({
                Param (
                    $PlotPath,
                    $Challenges,
                    $ChiaPath
                )
                $Leaf = Split-Path -Path $PlotPath -Leaf
                $KSize = $Leaf -split "plot-k" -split "-" | Select-Object -First 1 -Skip 1
                $PlotId = ($Leaf -split "-" | select -Last 1).Split(".")[0]
                $PlotObject = [PSCustomObject]@{
                    PSTypeName = "PSChiaPlotter.PlotTest"
                    Path = $PlotPath
                    PlotsFound = 0
                    ProofsFound = 0
                    Challenges = $Challenges
                    Ratio = 0.0
                    PlotSize = 0.0
                    LoadTime = 0.0
                    KSize = $KSize
                    PlotId = $PlotId
                    Errors = New-Object System.Collections.Generic.List[string]
                }
                
                $Proofs = "Proofs ([0-9]*) / ([0-9]*), ([0-9.]*)"
                $ValidPlot = "Found 1 valid plots"
                $InvalidPlot = "1 invalid plots found"
                $LoadedPlot = "Loaded a total of ([0-9]) plots of size ([0-9.]*) TiB, in ([0-9.]*) seconds"
                $ErrorString = "ERROR"

                if (Test-Path -Path $PlotPath -PathType Leaf){
                    $Results = chia.exe plots check -n $Challenges -g $PlotPath 2>&1 | Select-String -SimpleMatch "Proofs","Error","Found","Loaded"
                    foreach ($line in $Results){
                        switch -Regex ($line){
                            $Proofs {
                                $PlotObject.ProofsFound = $Matches[1] -as [int]
                                $PlotObject.Ratio = $Matches[3] -as [double]
                                break;
                            }
                            $ValidPlot {
                                $PlotObject.Valid = $true
                            }
                            $InvalidPlot {
                                $PlotObject.Valid = $false
                            }
                            $LoadedPlot {
                                $PlotObject.PlotsFound = $Matches[1] -as [int]
                                $PlotObject.PlotSize = $Matches[2] -as [double]
                                $PlotObject.LoadTime = $Matches[3] -as [double]
                            }
                            $ErrorString {
                                $PlotObject.Errors.Add($line.ToString())
                            }
                        }
                    }
                    $PlotObject
                }
            }).AddParameters($Parameters)
            $CheckPlotScript.RunspacePool = $RunspacePool
            $Handle = $CheckPlotScript.BeginInvoke()
            $temp = [PSCustomObject]@{
                PowerShell = $CheckPlotScript
                Handle = $Handle
            }
            [void]$jobs.Add($temp)
        } #foreach
    
        while ($jobs.handle.IsCompleted -contains $false){
            Write-Information "Returning objects and closing runspaces"
            $RemoveJobs = New-Object System.Collections.Generic.List[Object]
            $jobs | where {$_.handle.IsCompleted -eq $true} | foreach {
                $_.powershell.EndInvoke($_.handle)
                $_.PowerShell.Dispose()
                [void]$RemoveJobs.Add($_)
            }
            if ($RemoveJobs){
                foreach ($job in $RemoveJobs){
                    [void]$Jobs.Remove($job)
                }
            }
            $PercentComplete = [math]::Round(($TotalPlots - ($jobs | Measure-Object).Count) / $TotalPlots * 100,2)
            $ProgessParameters = @{
                Activity = "Running chia check on $TotalPlots plots"
                Status = "$($TotalPlots - ($jobs | Measure-Object).Count) / $TotalPlots Completed | $PercentComplete%" 
                PercentComplete = $PercentComplete
            }
            Write-Progress @ProgessParameters
        } #while
    } #process
    end{
        Write-Information "Closing Runspace Pool"
        $RunspacePool.close()
        $RunspacePool.Dispose()
    } #end
}
function Get-BestChiaFinalDrive {
    [CmdletBinding()]
    param(
        $ChiaVolumes,
        $ChiaJob,
        $ChiaQueue
    )
    $finalplotsize = $ChiaQueue.PlottingParameters.KSize.FinalSize

    $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,
        $ChiaQueue
    )

    $requiredTempSize = $ChiaQueue.PlottingParameters.KSize.TempSize
    $finalplotsize = $ChiaQueue.PlottingParameters.KSize.FinalSize

    $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){
        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
                $Log = @{
                    LogType = "INFO"
                    Message = "Chia Volume Found: Letter $($ChiaVolume.DriveLetter), UniqueId: $($ChiaVolume.UniqueID)"
                }
                Write-PSChiaPlotterLog @Log
            }
        }
        catch{
            Write-PSChiaPlotterLog -LogType "Warning" -Message "Unable to create a ChiaVolume from driveletter $($DriveLetter.DriveLetter)"
            Write-PSChiaPlotterLog -LogType ERROR -ErrorObject $_
        }
    } #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 -ErrorAction SilentlyContinue
                $Log = @{
                    LogType = "INFO"
                    Message = "Chia Volume Found: Letter $($ChiaVolume.DriveLetter), UniquieId: $($ChiaVolume.UniqueID)"
                }
                Write-PSChiaPlotterLog @Log
            }
        }
        catch{
            Write-PSChiaPlotterLog -LogType "Warning" -Message "Unable to create a ChiaVolume from driveletter $($DriveLetter.DriveLetter)"
            Write-PSChiaPlotterLog -LogType ERROR -ErrorObject $_
        }
    }
}
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 -Recurse | 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{
                Write-PSChiaPlotterLog -LogType "ERROR" -ErrorObject $_
                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.Queues[$queue].IsBlocked = $false
                    $Job.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 + 5)
                }
            }
        }
        catch{
            Write-PSChiaPlotterLog -LogType "ERROR" -ErrorObject $_
            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 -Recurse | 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

                while ($Queue.IsBlocked -or $Queue.Pause){
                    if ($Queue.Quit){
                        break
                    }
                    if (($Job.CompletedRunCount + $Job.RunsInProgress.Count) -ge $Job.TotalPlotCount){
                        break
                    }
                    if ($Queue.Pause){
                        $Queue.Status = "Paused"
                    }
                    else{
                        $Queue.Status = "Waiting"
                    }
                    Start-Sleep -Seconds 10
                }
                $Job.QueueLooping = $true;
                $Queue.IsBlocked = $false

                if ($Job.BasicPlotting){
                    $TempVolume = [PSChiaPlotter.ChiaVolume]::new($Queue.PlottingParameters.BasicTempDirectory)
                    $FinalVolume = [PSChiaPlotter.ChiaVolume]::new($Queue.PlottingParameters.BasicFinalDirectory)
                    if ($Queue.PlottingParameters.EnableBasicSecondTempDirectory){
                        $SecondTempVolume = [PSChiaPlotter.ChiaVolume]::new($Queue.PlottingParameters.BasicSecondTempDirectory)
                    }
                    $PhaseOneIsOpen = Test-PhaseOneIsOpen -ChiaJob $Job
                    while ($PhaseOneIsOpen -eq $false){
                        $Queue.Status = "Waiting - Phase 1 Limit"
                        if (($Job.CompletedRunCount + $Job.RunsInProgress.Count) -ge $Job.TotalPlotCount){
                            break
                        }
                        if ($Queue.Quit){
                            break
                        }
                        Start-Sleep -Seconds 15
                        $PhaseOneIsOpen = Test-PhaseOneIsOpen -ChiaJob $Job
                    }
                }
                else{
                    #grab a volume that has enough space
                    Do {
                        Try{
                            if ($Queue.Quit){
                                break
                            }
                            if (($Job.CompletedRunCount + $Job.RunsInProgress.Count) -ge $Job.TotalPlotCount){
                                break
                            }
                            Start-Sleep -Seconds 6
                            $PhaseOneIsOpen = Test-PhaseOneIsOpen -ChiaJob $Job
                            if (-not$PhaseOneIsOpen){
                                $Queue.Status = "Waiting - Phase 1 Limit"
                                Start-Sleep -Seconds 17
                                #do not need to check the drives if phase 1 is not open for another plot
                                continue
                            }

                            $TempVolume = Get-BestChiaTempDrive -ChiaVolumes $Job.TempVolumes -ChiaJob $Job -ChiaQueue $Queue
                            $FinalVolume = Get-BestChiaFinalDrive $Job.FinalVolumes -ChiaJob $Job -ChiaQueue $Queue
                            if ($TempVolume -eq $Null){
                                $Queue.Status = "Waiting on Temp Space"
                                Start-Sleep -Seconds 60
                            }
                            if ($FinalVolume -eq $Null){
                                $Queue.Status = "Waiting on Final Dir Space"
                                Start-Sleep -Seconds 60
                            }
                        }
                        catch{
                            $Queue.Status = "Failed To Grab Volume Info"
                            Write-Error -LogType "Error" -ErrorObject $_
                            Start-Sleep -Seconds 30
                        }
                    }
                    while ($TempVolume -eq $null -or $FinalVolume -eq $null -or $PhaseOneIsOpen -eq $false)
                } #else

                $Job.QueueLooping = $false
                $BlockedQueue = $Job.Queues | where {$_.IsBlocked -and !$_.Pause} | sort QueueNumber | select -First 1
                if ($BlockedQueue -ne $Null){
                    $BlockedQueue.IsBlocked = $false
                    $BlockedQueue = $null
                }
                if (($Job.CompletedRunCount + $Job.RunsInProgress.Count) -ge $Job.TotalPlotCount){
                    break
                }
                if ($Queue.Quit){
                    break
                }

                $Queue.Status = "Running"
                $plottingParameters = [PSChiaPlotter.ChiaParameters]::New($Queue.PlottingParameters)
                $plottingParameters.TempVolume = $TempVolume
                $plottingParameters.FinalVolume = $FinalVolume
                if ($Job.BasicPlotting){
                    $plottingParameters.SecondTempVolume = $SecondTempVolume
                }
                $newRun = [PSChiaPlotter.ChiaRun]::new($Queue,$runNumber,$plottingParameters)
                if ($DataHash.Debug){
                    Start-GUIDebugRun -ChiaRun $newRun -ChiaQueue $Queue -ChiaJob $Job
                }
                else{
                    Start-GUIChiaPlotting -ChiaRun $newRun -ChiaQueue $Queue -ChiaJob $Job
                }

                $QueuesBlocked = ($Job.Queues | where {$_.IsBlocked -and !$_.pause} | Measure-Object).Count
                if ($QueuesBlocked -eq 0 -and $Job.QueueLooping -eq $false -and !$Queue.Pause){
                    $Queue.IsBlocked = $false
                }
                else{
                    $Queue.IsBlocked = $true
                }
                #sleep to give some time for updating
                sleep 2
            } #for
            $Queue.IsBlocked = $false

            $Queue.Status = "Finished"
        }
        catch{
            Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
            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
            }
            $Queue.Status = "Failed"
        }
    }.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 -Recurse | ForEach-Object {Import-Module $_.FullName}
            Get-childItem -Path $DataHash.Classes -File | ForEach-Object {Import-Module $_.FullName}

            Import-Module -Name PSChiaPLotter
    
            $XAMLPath = Join-Path -Path $DataHash.WPF -ChildPath MainWindow.xaml
            $MainWindow = Import-Xaml -Path $XAMLPath

            #Assign GUI Controls To Variables
            $UIHash.MainWindow = $MainWindow

            #DataGrid
            $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")

            #Buttons
            $UIHash.NewJob_Button = $MainWindow.FindName("AddJob_Button")
            $UIHash.QuitJob_Button = $MainWindow.FindName("QuitJob_Button")
            $UIHash.PauseAllQueues_Button = $MainWindow.FindName("PauseAllQueues_Button")
            $UIHash.OpenLog_Button = $MainWindow.FindName("OpenLogButton")
            $UIHash.Refreshdrives_Button = $MainWindow.FindName("RefreshdrivesButton")
            $UIHash.CheckForUpdate_Button = $MainWindow.FindName("CheckForUpateButton")
            $UIHash.PauseQueue_Button = $MainWindow.FindName("PauseQueue_Button")
            $UIHash.QuitQueue_Button = $MainWindow.FindName("QuitQueue_Button")
            $DataHash.RefreshingDrives = $false

            $DataHash.MainViewModel = [PSChiaPlotter.MainViewModel]::new()
            $DataHash.MainViewModel.Version = (Get-Module -Name PSChiaPlotter).Version.ToString()
            $DataHash.MainViewModel.LogPath = $DataHash.LogPath
            $DataHash.MainViewModel.LogLevel = "Error"

            $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{
                    Invoke-NewJobButtonClick
                }
                catch{
                    Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
                }
            })

            $UIHash.QuitJob_Button.Add_Click({
                try{
                    Invoke-QuitJobButtonClick
                }
                catch{
                    Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
                }
            })

            $UIHash.QuitQueue_Button.Add_Click({
                try{
                    Invoke-QuitQueueButtonClick
                }
                catch{
                    Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
                }
            })

            $UIHash.PauseQueue_Button.Add_Click({
                try{
                    Invoke-PauseQueueButtonClick
                }
                catch{
                    Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
                }
            })

            $UIHash.PauseAllQueues_Button.Add_Click({
                try{
                    Invoke-PauseAllQueuesButtonClick
                }
                catch{
                    Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
                }
            })

            $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
                }
            })

            $UIHash.CheckForUpdate_Button.Add_Click({
                try{
                    Update-PSChiaPlotter
                }
                catch{
                    Write-PSChiaPlotterLog -LogType ERROR -ErrorObject $_
                    Show-Messagebox "Unable to check for updates... check logs for more info" | Out-Null
                }
            })

            $UIHash.OpenLog_Button.Add_Click({
                try{
                    Invoke-Item -Path $DataHash.MainViewModel.LogPath -ErrorAction Stop
                }
                catch{
                    Write-PSChiaPlotterLog -LogType ERROR -ErrorObject $_
                    Show-Messagebox "Unable to open log file, check the path '$($DataHash.MainViewModel.LogPath)'" | Out-Null
                }
            })

            #Datagrid Selection Change
            $UIHash.Queues_DataGrid.Add_SelectionChanged({
                try{
                    Invoke-QueueSelectionChange
                }
                catch{
                    Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
                }
            })

            $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(
        $ChiaRun,
        $ChiaQueue,
        $ChiaJob
    )

    try{
        #not really needed, but just wanted to make each parameter its own variable
        $PlottingParameters = $ChiaRun.PlottingParameters
        $KSize = $PlottingParameters.KSize.KSizeValue
        $Buffer = $PlottingParameters.RAM
        $Threads = $PlottingParameters.Threads
        $DisableBitfield = $PlottingParameters.DisableBitField
        $ExcludeFinalDirectory = $PlottingParameters.ExcludeFinalDirectory
        $TempDirectoryPath = $PlottingParameters.TempVolume.DirectoryPath
        $FinalDirectoryPath = $PlottingParameters.FinalVolume.DirectoryPath
        $LogDirectoryPath = $PlottingParameters.LogDirectory
        $SecondTempDirectoryPath = $PlottingParameters.SecondTempVolume.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 ($ChiaJob.BasicPlotting){
            if ($ChiaRun.PlottingParameters.EnableBasicSecondTempDirectory){
                if (-not[string]::IsNullOrWhiteSpace($SecondTempDirectoryPath)){
                    $SecondTempDirectoryPath = $SecondTempDirectoryPath.TrimEnd('\')
                    $ChiaArguments += " -2 `"$SecondTempDirectoryPath`"" 
                }
            }
        }
        else{
            if (-not[string]::IsNullOrWhiteSpace($SecondTempDirectoryPath)){
                $SecondTempDirectoryPath = $SecondTempDirectoryPath.TrimEnd('\')
                $ChiaArguments += " -2 `"$SecondTempDirectoryPath`"" 
            }
        }
        if ($KSize -eq 25){
            $ChiaArguments += " --override-k"
        }

        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)

                if (-not$ChiaJob.BasicPlotting){
                    $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
                        $ChiaRun.PlotId = $plotid
                        $ChiaQueue.CurrentTime = [DateTime]::Now
                        $ChiaRun.CurrentTime = [DateTime]::Now
                        $ChiaRun.Phase = $progress.Phase
                        if ($progress.EST_TimeRemaining.TotalSeconds -le 0){
                            $ChiaRun.EstTimeRemaining = New-TimeSpan -Seconds 0
                        }
                        else{
                            $ChiaRun.EstTimeRemaining = $progress.EST_TimeRemaining
                        }
                        switch ($progress.Phase) {
                            "Phase 1" {$ChiaRun.CurrentPhaseProgress = $progress.Phase1Progess}
                            "Phase 2" {$ChiaRun.CurrentPhaseProgress = $progress.Phase2Progess}
                            "Phase 3" {$ChiaRun.CurrentPhaseProgress = $progress.Phase3Progess}
                            "Phase 4" {$ChiaRun.CurrentPhaseProgress = $progress.Phase4Progess}
                            "Copying" {$ChiaRun.CurrentPhaseProgress = $progress.CopyProgess}
                        }
                        $ChiaRun.TempSize = Get-ChiaTempSize -DirectoryPath $TempDirectoryPath -PlotId $plotid
                        Start-Sleep (5 + $ChiaQueue.QueueNumber)
                    }
                    catch{
                        Start-Sleep 30
                    }
                } #while

                $ChiaJob.RunsInProgress.Remove($ChiaRun)
                $ChiaJob.CompletedRunCount++

                if (-not$ChiaJob.BasicPlotting){
                    $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)
                    sleep -Seconds 1
                    Get-ChildItem -Path $TempDirectoryPath -Filter "*$plotid*.tmp" | foreach {
                        try{
                            Remove-Item -Path $_.FullName -Force -ErrorAction Stop
                        }
                        catch{
                            Show-Messagebox -Text $_.Exception.Message | Out-Null
                        }
                    }
                    if (-not[string]::IsNullOrWhiteSpace($SecondTempDirectoryPath)){
                        Get-ChildItem -Path $SecondTempDirectoryPath -Filter "*$plotid*.tmp" | foreach {
                            try{
                                Remove-Item -Path $_.FullName -Force -ErrorAction Stop
                            }
                            catch{
                                Show-Messagebox -Text $_.Exception.Message | Out-Null
                            }
                        } #foreach file
                    }
                }
                else{
                    $ChiaRun.Status = "Completed"
                    $ChiaJob.CompletedPlotCount++
                    $ChiaQueue.CompletedPlotCount++
                    $DataHash.MainViewModel.CompletedRuns.Add($ChiaRun)
                    $ChiaRun.CheckPlotPowershellCommand = "&'$ChiaPath' plots check -g $plotid"
                    Update-ChiaGUISummary -Success
                }
                $ChiaQueue.CurrentRun = $null
                $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 (-not$ChiaJob.BasicPlotting){
                    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")
        }
    }
    catch{
        Write-PSChiaPlotterLog -LogType ERROR -ErrorObject $_
        $PSCmdlet.WriteError($_)
    }
}
function Start-GUIDebugRun{
    [CmdletBinding()]
    param(
        $ChiaRun,
        $ChiaQueue,
        $ChiaJob
    )
    try{
        $PlottingParameters = $ChiaRun.PlottingParameters
        $KSize = $PlottingParameters.KSize.KSizeValue
        $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.SecondTempVolume.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{
            Write-PSChiaPlotterLog -LogType ERROR -ErrorObject $_
        }
    }
    $RunningRunspaces = $DataHash.Runspaces
    foreach ($runspace in $RunningRunspaces){
        try{
            $runspace.Stop()
        }
        catch{
            Write-PSChiaPlotterLog -LogType ERROR -ErrorObject $_
        }
    }
}
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 100){
        return "RAM needs to be greater than 100"
    }
    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.EnablePhaseOneLimitor){
        if ($NewJob.PhaseOneLimit -lt 1){
            return "Phase one limit cannot be less than 1... that doesn't make sense."
        }
    }
    if ($NewJob.BasicPlotting){
        if (-not[System.IO.Directory]::Exists($ChiaParameters.BasicTempDirectory)){
            return "Temp Directory `"$($ChiaParameters.BasicTempDirectory)`" does not exists"
        }
        if (-not[System.IO.Directory]::Exists($ChiaParameters.BasicFinalDirectory)){
            return "Final Directory `"$($ChiaParameters.BasicFinalDirectory)`" does not exists"
        }
        if ($ChiaParameters.EnableBasicSecondTempDirectory){
            if (-not[System.IO.Directory]::Exists($ChiaParameters.BasicSecondTempDirectory)){
                return "2nd Temp Directory `"$($ChiaParameters.BasicSecondTempDirectory)`" does not exists"
            }
        }
    }
    else{
        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"})"
            }
        }
    } #else
    
    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 Test-PhaseOneIsOpen {
    [CmdletBinding()]
    param(
        $ChiaJob
    )

    try{
        if ($ChiaJob.EnablePhaseOneLimitor){
            $phaseOneCount = ($ChiaJob.RunsInProgress | where Phase -eq "Phase 1" | Measure-Object).Count
            if ($phaseOneCount -ge $ChiaJob.PhaseOneLimit){
                return $false
            }
            else{
                return $true
            }
        }
        else{
            return $true
        }
    }
    catch{
        Write-PSChiaPlotterLog -LogLevel "Warning" -Message "Unable to check if phase one is open!"
    }
}
function Update-ChiaGUISummary{
    [CmdletBinding()]
    param(
        [switch]$Success,
        [switch]$Failed
    )

    if ($Success){
        $OneDayAgo = (Get-Date).AddDays(-1)
        $PlotsIn24Hrs = $DataHash.MainViewModel.CompletedRuns | where ExitTime -GT $OneDayAgo
        $DataHash.MainViewModel.PlotPlottedPerDay = ($PlotsIn24Hrs | Measure-Object).Count
        $totalTBPlotted = 0
        foreach ($plot in $PlotsIn24Hrs){
            $totalTBPlotted += ($plot.PlottingParameters.KSize.FinalSize / 1gb)
        }
        $DataHash.MainViewModel.TBPlottedPerDay = [math]::Round($totalTBPlotted / 1000,4)

        $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 Update-PSChiaPlotter {
    [CmdletBinding()]
    param()

    $UpdateScript = [powershell]::Create().AddScript{
        Add-Type -AssemblyName PresentationFramework
        Add-Type -AssemblyName System.Windows.Forms
        Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName}
        Get-childItem -Path $DataHash.Classes -File | ForEach-Object {Import-Module $_.FullName}
        Import-Module -Name PSChiaPlotter

        $CurrentModule = Get-Module -Name PSChiaPlotter
        $NewestModule = Find-Module -Name PSChiaPLotter -Repository PSGallery
        if ($NewestModule.Version -gt $CurrentModule.Version){
            $Response = Show-Messagebox -Text "New version found! Version - $($NewestModule.Version.ToString())`nWould you like to update now?" -Buttons YesNo
            if ($Response -eq [System.Windows.MessageBoxResult]::Yes){
                try{
                    Update-Module -Name PSChiaPlotter -Force -ErrorAction Stop
                    $message = "PSChiaPlotter module successfully updated from $($CurrentModule.Version.ToString()) to $($NewestModule.Version.ToString())"
                    $message += "`nYou must restart the GUI before changes can take effect.`nOnly do this when your plots have finished!"
                    Write-PSChiaPlotterLog -LogType INFO -Message $message
                    Show-Messagebox -Text $message | Out-Null
                }
                catch{
                    Write-PSChiaPlotterLog -LogType ERROR -ErrorObject $_
                    Show-Messagebox -Text "Unable to update to the latest version. Check logs more info" | Out-Null
                }
            }
        }
        else{
            Try{
                Show-Messagebox -Text "Your PSChiaPlotter is up to date!" | Out-Null
            }
            catch{
                Write-PSChiaPlotterLog -LogType ERROR -ErrorObject $_
            }
        }
    }
    $UpdateScript.RunspacePool = $ScriptsHash.Runspacepool
    $UpdateScript.BeginInvoke()
}
function Write-PSChiaPlotterLog {
    [CmdletBinding()]
    param(
        [ValidateSet("INFO","WARNING","ERROR")]
        [string]$LogType,
        [string]$Message,
        [int]$LineNumber,
        [string]$Line,
        $ErrorObject
    )

    try{
        $Date = Get-Date -Format "[yyyy-MM-dd.HH:mm:ss]"
        switch ($LogType){
            "ERROR" {
                $Message = $ErrorObject.Exception.Message
                $LineNumber = $ErrorObject.InvocationInfo.ScriptLineNumber
                $Line = $ErrorObject.InvocationInfo.Line
                Write-Host "[$LogType]$Date-$LineNumber-$Message`n $Line"
                break
            }
            "WARNING" {
                if ($DataHash.MainViewModel.LogLevel -eq "WARNING"){
                    Write-Host "[$LogType]-$Date-$Message"
                }
                break
            }
            "INFO" {
                if ($DataHash.MainViewModel.LogLevel -eq "INFO" -or $DataHash.MainViewModel.LogLevel -eq "WARNING"){
                    Write-Host "[$LogType]-$Date-$Message"
                }
                break
            }
        }
    }
    catch{
        Write-Host $_.Exception.Message
    }
}
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
}
function Invoke-LoadJobButtonClick {
    [CmdletBinding()]
    param()

    try{
        $JobFilePath = $SavedJobs_ComboBox.SelectedValue
        if (($JobFilePath -ne $null) -and (Test-Path $JobFilePath)){

            #Have to transfer the properties over since the imported job is Desesersilzed Object

            $ImportedJob = Import-Clixml -Path $JobFilePath
            Write-PSChiaPlotterLog -LogType "INFO" -Message "Imported Job"

            $newSavedJob = [PSChiaPlotter.ChiaJob]::new()
            $newSavedJob.JobNumber = $jobNumber
            $newSavedJob.Status = "Waiting"
            $NewSavedJobViewModel = [PSChiaPlotter.NewJobViewModel]::new($newSavedJob)

            $SkipJobProperties = @("InitialChiaParameters","Queues","RunsInProgress","TempVolumes","FinalVolumes")
            $JobProperties = ($ImportedJob.NewChiaJob | Get-Member -MemberType Property).Name
            $JobProperties = $JobProperties | where {$_ -notin $SkipJobProperties}
            foreach ($property in $JobProperties){
                try{
                    $NewSavedJobViewModel.NewChiaJob.$property = $ImportedJob.NewChiaJob.$property
                }
                catch{
                    Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
                }
            }

            $SkipParameterProperties = @("SecondTempVolume","KSize")
            $ParameterProperties = ($ImportedJob.NewChiaJob.InitialChiaParameters | Get-Member -MemberType Property).Name
            $ParameterProperties = $ParameterProperties | where {$_ -notin $SkipParameterProperties}
            foreach ($property in $ParameterProperties){
                try{
                    $NewSavedJobViewModel.NewChiaJob.InitialChiaParameters.$property = $ImportedJob.NewChiaJob.InitialChiaParameters.$property
                }
                catch{
                    Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
                }
            }

            $NewSavedJobViewModel.NewChiaJob.JobNumber = $jobNumber
            $NewSavedJobViewModel.NewChiaJob.Status = "Waiting"
            $NewSavedJobViewModel.NewChiaJob.StartTime = Get-Date

            Get-ChiaVolume | foreach {
                $NewSavedJobViewModel.FinalAvailableVolumes.Add($_)
            }
            $NewSavedJobViewModel.FinalAvailableVolumes | foreach {
                $NewSavedJobViewModel.SecondTempVolumes.Add([PSChiaPlotter.ChiaVolume]::new($_))
                $NewSavedJobViewModel.TempAvailableVolumes.Add([PSChiaPlotter.ChiaVolume]::new($_))
            }

            #need to update directory paths for each volume...
            $NewSavedJobViewModel.TempAvailableVolumes | foreach {
                $FoundVolume = $ImportedJob.TempAvailableVolumes | where UniqueId -eq $_.UniqueId
                if ($FoundVolume -ne $Null){
                    $_.DirectoryPath = $FoundVolume.DirectoryPath
                }
                else{
                    $FoundVolume = $ImportedJob.NewChiaJob.TempVolumes | where UniqueId -eq $_.UniqueId
                    if ($FoundVolume -ne $Null){
                        $_.DirectoryPath = $FoundVolume.DirectoryPath
                    }
                }
                $FoundVolume = $Null
            }
            $NewSavedJobViewModel.FinalAvailableVolumes | foreach {
                $FoundVolume = $ImportedJob.FinalAvailableVolumes | where UniqueId -eq $_.UniqueId
                if ($FoundVolume -ne $Null){
                    $_.DirectoryPath = $FoundVolume.DirectoryPath
                }
                else{
                    $FoundVolume = $ImportedJob.NewChiaJob.FinalVolumes | where UniqueId -eq $_.UniqueId
                    if ($FoundVolume -ne $Null){
                        $_.DirectoryPath = $FoundVolume.DirectoryPath
                    }
                }
                $FoundVolume = $Null
            }
            $NewSavedJobViewModel.SecondTempVolumes | foreach {
                $FoundVolume = $ImportedJob.SecondTempVolumes | where UniqueId -eq $_.UniqueId
                if ($FoundVolume -ne $Null){
                    $_.DirectoryPath = $FoundVolume.DirectoryPath
                }
                $FoundVolume = $Null
            }

            $NewSavedJobViewModel.AvailableKSizes = $NewJobViewModel.AvailableKSizes

            $SecondTempVolume = $NewSavedJobViewModel.SecondTempVolumes | where UniqueId -eq $ImportedJob.NewChiaJob.InitialChiaParameters.SecondTempVolume.UniqueId
            $NewSavedJobViewModel.NewChiaJob.InitialChiaParameters.SecondTempVolume = $SecondTempVolume

            foreach ($Volume in $ImportedJob.NewChiaJob.TempVolumes){
                $FoundTempVolume = $NewSavedJobViewModel.TempAvailableVolumes | where UniqueId -eq $Volume.UniqueId
                if ($FoundTempVolume){
                    $NewSavedJobViewModel.AddTempVolume($FoundTempVolume)
                }
                $FoundTempVolume = $null
            }
            $Volume = $null
            foreach ($Volume in $ImportedJob.NewChiaJob.FinalVolumes){
                $FoundTempVolume = $NewSavedJobViewModel.FinalAvailableVolumes | where UniqueId -eq $Volume.UniqueId
                if ($FoundTempVolume){
                    $NewSavedJobViewModel.AddFinalVolume($FoundTempVolume)
                }
                $FoundTempVolume = $null
            }

            $DataHash.NewJobViewModel = $NewSavedJobViewModel
            $UIHash.NewJob_Window.DataContext = $NewSavedJobViewModel

            #Combobox wouldn't automatically update for some reason
            switch ($ImportedJob.NewChiaJob.InitialChiaParameters.KSize.KSizeValue){
                25 {$Index = 0;break}
                32 {$Index = 1;break}
                33 {$Index = 2;break}
                34 {$Index = 3;break}
                35 {$Index = 4;break}
                default {$Index = 1}
            }
            $KSize_ComboBox.SelectedIndex = $Index
            $DataHash.NewJobViewModel.NewChiaJob.InitialChiaParameters.RAM = $ImportedJob.NewChiaJob.InitialChiaParameters.RAM

            if ($DataHash.NewJobViewModel.NewChiaJob.BasicPlotting){
                $AdvancedBasic_Button.Content = "Switch To Advance"
                $AdvancedPlotting_TabControl.Visibility = [System.Windows.Visibility]::Collapsed
                $BasicPlotting_Grid.Visibility = [System.Windows.Visibility]::Visible
            }
        }
    }
    catch{
        Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
        Show-MessageBox -Message "Unable to laod previous job :( Check logs for more info"
    }
}
function Invoke-NewJobButtonClick {
    [CmdletBinding()]
    param()

    try{
        $SavedJobsPath = Join-Path -Path $ENV:LOCALAPPDATA -ChildPath 'PSChiaPlotter\SavedJobs'
        $SavedJobsList = New-Object -TypeName System.Collections.Generic.List[Object]
        if ([System.IO.Directory]::Exists($SavedJobsPath)){
            Get-ChildItem -Path $SavedJobsPath | foreach {
                try{
                    $savedjob = [pscustomobject]@{
                        Name = $_.BaseName
                        FullName = $_.FullName
                    }
                    $SavedJobsList.Add($savedjob)
                }
                catch{
                    Write-Error -LogType "Error" -ErrorObject $_
                }
            }
        }

        $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"
        $newJob.Status = "Waiting"
        $NewJobViewModel = [PSChiaPlotter.NewJobViewModel]::new($newJob)
        $DataHash.NewJobViewModel = $NewJobViewModel
        $UIHash.NewJob_Window.DataContext = $NewJobViewModel

        #Combobox
        $KSize_ComboBox = $UIHash.NewJob_Window.FindName("KSize_ComboBox")
        $KSize_ComboBox.SelectedIndex = 1

        $SavedJobs_ComboBox = $UIHash.NewJob_Window.FindName("SavedJobs_Combobox")
        $SavedJobs_ComboBox.ItemsSource = $SavedJobsList

        #Buttons
        $AdvancedBasic_Button = $UIHash.NewJob_Window.FindName("AdvancedBasic_Button")
        $LoadJob_Button = $UIHash.NewJob_Window.FindName("LoadJob_Button")
        $CreateJob_Button = $UIHash.NewJob_Window.FindName("CreateJob_Button")
        $CancelJobCreation_Button = $UIHash.NewJob_Window.FindName("CancelJobCreation_Button")
        $SaveJob_Button = $UIHash.NewJob_Window.FindName("SaveJob_Button")
        $ClearTempVolume_Button = $UIHash.NewJob_Window.FindName("RemoveTempVolumeButton")

        #TabControl
        $AdvancedPlotting_TabControl = $UIHash.NewJob_Window.FindName("AdvancedPlotting_TabControl")
        
        #Grid
        $BasicPlotting_Grid = $UIHash.NewJob_Window.FindName("BasicPlotting_Grid")

        #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.FinalAvailableVolumes.Add($_)
        }
        $NewJobViewModel.FinalAvailableVolumes | foreach {
            $NewJobViewModel.SecondTempVolumes.Add([PSChiaPlotter.ChiaVolume]::new($_))
            $NewJobViewModel.TempAvailableVolumes.Add([PSChiaPlotter.ChiaVolume]::new($_))
        }

        $AdvancedBasic_Button.Add_Click({
            try{
                if ($AdvancedBasic_Button.Content -eq "Switch To Basic"){
                    $AdvancedBasic_Button.Content = "Switch To Advance"
                    $DataHash.NewJobViewModel.NewChiaJob.BasicPlotting = $true
                    $AdvancedPlotting_TabControl.Visibility = [System.Windows.Visibility]::Collapsed
                    $BasicPlotting_Grid.Visibility = [System.Windows.Visibility]::Visible
                }
                else{
                    $AdvancedBasic_Button.Content = "Switch To Basic"
                    $DataHash.NewJobViewModel.NewChiaJob.BasicPlotting = $false
                    $BasicPlotting_Grid.Visibility = [System.Windows.Visibility]::Collapsed
                    $AdvancedPlotting_TabControl.Visibility = [System.Windows.Visibility]::Visible
                }
            }
            catch{
                Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
            }
        })

        $LoadJob_Button.Add_Click({
            try{
                Invoke-LoadJobButtonClick
            }
            catch{
                Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
            }
        })

       $KSize_ComboBox.Add_SelectionChanged({
            try{
                $KSizeTempSize = $DataHash.NewJobViewModel.NewChiaJob.InitialChiaParameters.KSize.TempSize
                $KSizeFinalSize = $DataHash.NewJobViewModel.NewChiaJob.InitialChiaParameters.KSize.FinalSize
                $DataHash.NewJobViewModel.NewChiaJob.InitialChiaParameters.RAM = $DataHash.NewJobViewModel.NewChiaJob.InitialChiaParameters.KSize.MinRAM
                foreach ($volume in $DataHash.NewJobViewModel.TempAvailableVolumes){
                    $max = [math]::Floor([decimal]($volume.size / $KSizeTempSize))
                    $volume.MaxConCurrentTempChiaRuns = $max
                }
                foreach ($volume in $DataHash.NewJobViewModel.NewChiaJob.TempVolumes){
                    $max = [math]::Floor([decimal]($volume.size / $KSizeTempSize))
                    $volume.MaxConCurrentTempChiaRuns = $max
                }

                foreach ($volume in $DataHash.NewJobViewModel.FinalAvailableVolumes){
                    $max = [math]::Floor([decimal]($volume.FreeSpace / $KSizeFinalSize))
                    $volume.PotentialFinalPlotsRemaining = $max
                }
                foreach ($volume in $DataHash.NewJobViewModel.NewChiaJob.FinalVolumes){
                    $max = [math]::Floor([decimal]($volume.FreeSpace / $KSizeFinalSize))
                    $volume.PotentialFinalPlotsRemaining = $max
                }

                foreach ($volume in $DataHash.NewJobViewModel.SecondTempVolumes){
                    $max = [math]::Floor([decimal]($volume.size / $KSizeTempSize))
                    $volume.MaxConCurrentTempChiaRuns = $max
                }
            }
            catch{
                Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
            }
        })

        $CreateJob_Button.add_Click({
            try{
                if ($DataHash.NewJobViewModel.NewChiaJob.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 ($DataHash.NewJobViewModel.NewChiaJob.InitialChiaParameters.KSize.KSizeValue -eq 25){
                    $response = Show-Messagebox -Text "KSize 25 is only for testing and cannot farm XCH, continue?" -Button YesNo -Icon Warning
                    if ($response -eq [System.Windows.MessageBoxResult]::No){
                        return
                    }
                }
                $Results = Test-ChiaParameters $DataHash.NewJobViewModel.NewChiaJob
                if ($Results -ne $true){
                    Show-Messagebox -Text $Results -Title "Invalid Parameters" -Icon Warning
                    return
                }
                $DataHash.MainViewModel.AllJobs.Add($DataHash.NewJobViewModel.NewChiaJob)
                $newJobRunSpace = New-ChiaJobRunspace -Job $DataHash.NewJobViewModel.NewChiaJob
                $newJobRunSpace.Runspacepool = $ScriptsHash.RunspacePool
                [void]$newJobRunSpace.BeginInvoke()
                $DataHash.Runspaces.Add($newJobRunSpace)
                $UIHash.NewJob_Window.Close()
            }
            catch{
                Write-PSChiaPlotterLog -LogType "ERROR" -ErrorObject $_
                Show-Messagebox -Text $_.Exception.Message -Title "Create New Job Error" -Icon Error
            }
        })

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

        $SaveJob_Button.Add_Click({
            try{
                $PSChiaPlotterFolderPath = Join-Path -Path $ENV:LOCALAPPDATA -ChildPath 'PSChiaPlotter\SavedJobs'
                if (-not[System.IO.Directory]::Exists($PSChiaPlotterFolderPath)){
                    New-Item -Path $PSChiaPlotterFolderPath -ItemType Directory | Out-Null
                }
                if ([string]::IsNullOrWhiteSpace($DataHash.NewJobViewModel.NewChiaJob.JobName)){
                    Show-Messagebox -Text "Please give your a job a name first" | Out-Null
                    return
                }
                $SaveJobPath = Join-Path -Path $PSChiaPlotterFolderPath -ChildPath "$($DataHash.NewJobViewModel.NewChiaJob.JobName).xml"
                if (Test-Path $SaveJobPath){
                    $Response = Show-MessageBox -Text "Job $($DataHash.NewJobViewModel.NewChiaJob.JobName) already exists, would you like to overwrite it?" -Buttons YesNo
                    if ($Response -eq [System.Windows.MessageBoxResult]::Yes){
                        $DataHash.NewJobViewModel | Export-Clixml -Path $SaveJobPath -Depth 10 -Force
                        Show-MessageBox "$($DataHash.NewJobViewModel.NewChiaJob.JobName) job saved to $PSChiaPlotterFolderPath"
                        return
                    }
                }
                $DataHash.NewJobViewModel | Export-Clixml -Path $SaveJobPath -Depth 10
                Show-MessageBox "$($DataHash.NewJobViewModel.NewChiaJob.JobName) job saved to $PSChiaPlotterFolderPath"
            }
            catch{
                Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
            }
        })

        $ClearTempVolume_Button.Add_Click({
            try{
                $tempVolume = $DataHash.NewJobViewModel.NewChiaJob.InitialChiaParameters.SecondTempVolume
                if ($tempVolume -ne $Null){
                    Write-PSChiaPlotterLog -LogType "INFO" -Message "Clearing Temp Volume Selection"
                    $DataHash.NewJobViewModel.NewChiaJob.InitialChiaParameters.SecondTempVolume = $null
                }
            }
            catch{
                Write-PSChiaPlotterLog -LogType ERROR -ErrorObject $_
            }
        })

        $UIHash.NewJob_Window.ShowDialog()
    }
    catch{
        Write-PSChiaPlotterLog -LogType ERROR -ErrorObject $_
        Show-Messagebox -Text $_.Exception.Message -Title "Create New Job Error" -Icon Error
    }
}
function Invoke-PauseAllQueuesButtonClick {
    [CmdletBinding()]
    param()

    try{
        $Message = "Pausing Queues does not end the current running chia process."
        $message += " It will only prevent new processes from starting."
        $message += "`n`nAre you sure you want to pause all queues?"
        $Response = Show-MessageBox -Text $message -Buttons YesNo -Icon Information
        if ($Response -eq [System.Windows.MessageBoxResult]::Yes){
            $AllCurrentQueues = $DataHash.MainViewModel.AllQueues | where {$_.Status -eq "Running" -or $_.Status -eq "Waiting"}
            foreach ($queue in $AllCurrentQueues){
                $Queue.PauseResumeQueue()
            }
        }
    }
    catch{
        Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
    }
}
function Invoke-PauseQueueButtonClick{
    [CmdletBinding()]
    param()

    try{
        $SelectedQueue = $UIHash.Queues_DataGrid.SelectedItem
        $Pauseable = $SelectedQueue.Status -eq "Running" -or $SelectedQueue.Status -eq "Waiting" -or $SelectedQueue.Pause
        if ($SelectedQueue -ne $Null){
            if ($Pauseable){
                $SelectedQueue.PauseResumeQueue()
                $UIHash.PauseQueue_Button.Content = $SelectedQueue.ButtonContent
            }
            else{
                Show-MessageBox -Text "Only Queues that are currenting running or waiting can be paused" -Icon Warnin
            }
        }
        else{
            Show-MessageBox -Text "No Queue was selected!" -Icon Warning
        }
    }
    catch{
        Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
        Show-MessageBox -Text "Unable To Pause Queue! Check logs for more info" -Icon Error
    }
}
function Invoke-QuitJobButtonClick{
    [CmdletBinding()]
    param()

    try{
        $SelectedJob = $Null
        $SelectedJob = $UIHash.Jobs_DataGrid.SelectedItem
        if ($SelectedJob){
            if ($SelectedJob.Status -eq "Completed" -or $SelectedJob.Status -eq "Quitting"){
                Show-MessageBox -Text "This Job is either completed or in the process of quitting" -Icon Information
                return
            }
            $Message = "Are you sure you want to quit job - $($SelectedJob.JobName)?"
            $Message += "`nAll running chia processes under this job will be cancelled!"
            $Response = Show-MessageBox -Text $Message -Buttons YesNo
            if ($Response -eq [System.Windows.MessageBoxResult]::Yes){
                $runningQueues = $SelectedJob.Queues | where Status -ne "Finished" | where Status -ne "Failed"
                foreach ($queue in $runningQueues){
                    $queue.Quit = $true
                    $queue.Status = "Quitting"
                }
                foreach ($run in $SelectedJob.RunsInProgress){
                    $run.ChiaProcess.Kill()
                }
                $SelectedJob.Status = "Completed"
            }
        }
        else{
            Show-MessageBox -Text "No Job Selected!" -Icon Warning
        }
    }
    catch{
        Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
    }
}
function Invoke-QuitQueueButtonClick{
    [CmdletBinding()]
    param()

    try{
        $SelectedQueue = $UIHash.Queues_DataGrid.SelectedItem
        if ($SelectedQueue){
            if ($SelectedQueue.Status -ne "Finished"){
                $SelectedQueue.QuitQueue()
                $UIHash.QuitQueue_Button.IsEnabled = $false
                $UIHash.PauseQueue_Button.IsEnabled = $false
            }
            else{
                Show-MessageBox -Text "Queue has already Finished!" -Icon Information
            }
        }
        else{
            Show-MessageBox -Text "No Queue was selected!" -Icon Warning
        }
    }
    catch{
        Show-MessageBox -Text "Unable To Quit Queue. Check logs for more info!" -Icon Error
        Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
    }
}
function Invoke-QueueSelectionChange{
    [CmdletBinding()]
    param()

    try{
        $SelectedQueue = $UIHash.Queues_DataGrid.SelectedItem
        if ($SelectedQueue){
            $UIHash.PauseQueue_Button.Content = $SelectedQueue.ButtonContent
            $UIHash.PauseQueue_Button.IsEnabled = $SelectedQueue.ButtonEnabled
            $UIHash.QuitQueue_Button.IsEnabled = $SelectedQueue.QuitButtonEnabled
        }
    }
    catch{
        Write-PSChiaPlotterLog -LogType "Error" -ErrorObject $_
    }
}
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, Test-ChiaPlot, Test-ChiaPlotParallel