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 = $Matches[2] / $Matches[6] } #psobject if (-not$Summary){ $harvesterActivity } else{ $SummaryLog.Add($harvesterActivity) } } } #switch } #foreach line if ($Summary.IsPresent -and $SummaryLog.Count -ne 0){ Write-Information "Computing Summary for $logfile" if ([System.Environment]::OSVersion.Platform -eq "Win32NT"){ $FirstandLast = $SummaryLog | Sort-Object Time -Descending | Select-Object -First 1 -Last 1 | Sort-Object -Descending $RunTime = $FirstandLast[1].Time - $FirstandLast[0].Time if ($RunTime.TotalMinutes -lt 0){$RunTime = $FirstandLast[0].Time - $FirstandLast[1].Time} if ($RunTime.TotalMinutes -ne 0){$ChallengesPerMinute = $SummaryLog.Count / $RunTime.TotalMinutes} } else{ Write-Warning "Unable to calculate average challenges per min on linux due the timestamps missing the date portion." } [PSCustomObject]@{ PSTypeName = "PSChiaPlotter.ChiaHarvesterSummary" RunTime = $RunTime TotalEligiblePlots = ($SummaryLog | Measure-Object EligiblePlots -Sum).Sum BestLookUpTime = ($SummaryLog | Measure-Object LookUpTime -Minimum).Minimum WorstLookUpTime = ($SummaryLog | Measure-Object LookUpTime -Maximum).Maximum AverageLookUpTime = ($SummaryLog | Measure-Object LookUpTime -Average).Average ProofsFound = ($SummaryLog | Measure-Object -Property ProofsFound -Sum).Sum FilterRatio = ($SummaryLog | Measure-Object -Property FilterRatio -Average).Average ChallengesPerMinute = $ChallengesPerMinute } } } catch{ $PSCmdlet.WriteError($_) } } #foreach } function Get-ChiaKPlotCombination{ [CmdletBinding(DefaultParameterSetName = "DriveLetter")] param( [Parameter(ParameterSetName="FreeSpace")] [int64[]]$FreeSpace, [Parameter(ParameterSetName="DriveLetter")] [string[]]$DriveLetter = (Get-Volume).DriveLetter ) if ($PSCmdlet.ParameterSetName -eq "FreeSpace"){ foreach ($space in $FreeSpace){ $Max = Get-MaxKSize -TotalBytes $space $AllCombos = Get-OptimizedKSizePlotNumbers $Max | sort RemainingBytes $AllCombos | Add-Member -MemberType NoteProperty -Name "StartingFreeSpace" -Value $space $AllCombos } } elseif ($PSCmdlet.ParameterSetName -eq "DriveLetter"){ foreach ($letter in $DriveLetter){ $Drive = Get-Volume -DriveLetter $letter $Max = Get-MaxKSize -TotalBytes $Drive.SizeRemaining $AllCombos = Get-OptimizedKSizePlotNumbers $Max | sort RemainingBytes $AllCombos | Add-Member -NotePropertyMembers @{ DriveLetter = $letter FriendlyName = $Drive.FileSystemLabel } $AllCombos | foreach {$_.psobject.TypeNames.Insert(0,"PSChiaPlotter.KSizeCombination")} $AllCombos } } } function Get-ChiaMaxParallelCount { [CmdletBinding()] param( [Parameter()] [ValidateRange(1,128)] [int]$ThreadCount = 2, [Parameter()] [ValidateRange(1, [int]::MaxValue)] [int]$BufferMiB = 3390 ) if (!$PSBoundParameters.ContainsKey("ThreadCount") -and !$PSBoundParameters.ContainsKey("BufferMiB")){ Write-Warning "All calculations based on plotting k32 plot size only. SSD TB suggestion rounded up to the nearest TB." } else{ Write-Warning "SSD TB suggestion rounded up to the nearest TB." } $Processor = Get-CimInstance -ClassName Win32_Processor $Threads = ($Processor | measure -Property ThreadCount -Sum).Sum $MaxParallelCountCPU = [math]::Floor($Threads / $ThreadCount) #1mb = 1048576 bytes $RAM = (Get-CimInstance -ClassName Win32_PhysicalMemory | measure -Property Capacity -Sum).Sum / 1mb $MaxParallelCountRAM = [Math]::Floor([decimal]($RAM / $BufferMiB)) $SystemDisk = Get-CimInstance -Namespace ROOT/Microsoft/Windows/Storage -ClassName MSFT_Disk -Filter "IsSystem=True" $SSDs = Get-CimInstance -Namespace root/microsoft/windows/storage -ClassName MSFT_PhysicalDisk -Filter "MediaType=4" #4 -eq SSD $SSDs = $SSDs | where UniqueId -ne $SystemDisk.UniqueId | select $One_TB = 1000000000000 $One_GB = 1000000000 $TotalSSDspace = ($SSDs | measure -Property Size -Sum).Sum $SSD_Count = ($SSDs | Measure-Object).Count if ($SSD_Count -eq 0){ Write-Warning "No non-system SSD found, therefore Current_MaxParallelPlots will be 0. (Ignore if using mutiple HDDs)" } if ($Threads -gt ($Processor.NumberOfCores * 2)){ Write-Warning "Threads may actually only be half what is reported and therefore all calculations are off." } $SSD_MAX = [math]::Floor([decimal]($TotalSSDspace / (256.6 * $One_GB))) if ($MaxParallelCountCPU -le $MaxParallelCountRAM){ $MAXCount = $MaxParallelCountCPU $BottleNeck = "CPU" } else{ $MAXCount = $MaxParallelCountRAM $BottleNeck = "RAM" } $Suggested_SSD_TB = [math]::Ceiling([decimal](256.6 * $MAXCount) / 1000) if ($SSD_MAX -le $MAXCount){ $CurrentMax = $SSD_MAX $BottleNeck = "SSD" } else{ $CurrentMax = $MAXCount } $Suggested_SSD_TB = [math]::Ceiling([decimal](256.6 * $MAXCount) / 1000) [PSCustomObject]@{ ThreadCount = $ThreadCount Buffer = $BufferMiB CPUTotalThreads = $Threads CPUCores = ($Processor | Measure -Property NumberOfCores -Sum).Sum NumberOfProcessors = ($Processor | measure).Count TotalRAM_MiB = $RAM BottleNeck = $BottleNeck Current_SSD_SPACE_TB = [math]::Round(($TotalSSDspace / $One_TB),2) Current_SSD_Count = $SSD_Count Suggested_SSD_SPACE_TB = $Suggested_SSD_TB Current_MaxParallelPlots = $CurrentMax Potential_MAXParallelPlots = $MAXCount } } function Get-ChiaPlotProgress { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateScript({Test-Path -Path $_})] [string]$LogPath ) if ([System.IO.Directory]::Exists($LogPath)){ Write-Error "You provided a directory path and not a file path to the log file" -ErrorAction Stop } #base code from https://github.com/swar/Swar-Chia-Plot-Manager/blob/7287eef4796dbfa4cc009086c6502d19f0706f3e/config.yaml.default $phase1_line_end = 801 $phase2_line_end = 834 $phase3_line_end = 2474 $phase4_line_end = 2620 $copyfile_line_end = 2627 $phase1_weight = 33 $phase2_weight = 20 $phase3_weight = 42 $phase4_weight = 3 $copyphase_weight = 2 $LogItem = Get-Item -Path $LogPath $StartTime = $LogItem.CreationTime $EndTime = Get-Date $ElaspedTime = New-TimeSpan -Start $StartTime -End $EndTime $LogFile = Get-Content -Path $LogPath $plotId = $LogFile | Select-String -SimpleMatch "ID: " | foreach {$_.ToString().Split(" ")[1]} $line_count = $LogFile.Count if ($line_count -ge $phase1_line_end){ $progress += $phase1_weight } else{ $progress += $phase1_weight * ($line_count / $phase1_line_end) $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $progress $secondsRemaining = [int]($Est_TimeRemaining - $ElaspedTime.TotalSeconds) return [PSCustomObject]@{ Progress = [math]::Round($progress,2) Phase = "Phase 1" ElaspedTime = $ElaspedTime EST_TimeReamining = New-TimeSpan -Seconds $secondsRemaining PlotId = $plotId } } if ($line_count -ge $phase2_line_end){ $progress += $phase2_weight } else{ $progress += $phase2_weight * (($line_count - $phase1_line_end) / ($phase2_line_end - $phase1_line_end)) $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $progress $secondsRemaining = [int]($Est_TimeRemaining - $ElaspedTime.TotalSeconds) return [PSCustomObject]@{ Progress = [math]::Round($progress,2) Phase = "Phase 2" ElaspedTime = $ElaspedTime EST_TimeReamining = New-TimeSpan -Seconds $secondsRemaining PlotId = $plotId } } if ($line_count -ge $phase3_line_end){ $progress += $phase3_weight } else{ $progress += $phase3_weight * (($line_count - $phase2_line_end) / ($phase3_line_end - $phase2_line_end)) $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $progress $secondsRemaining = [int]($Est_TimeRemaining - $ElaspedTime.TotalSeconds) return [PSCustomObject]@{ Progress = [math]::Round($progress,2) Phase = "Phase 3" ElaspedTime = $ElaspedTime EST_TimeReamining = New-TimeSpan -Seconds $secondsRemaining PlotId = $plotId } } if ($line_count -ge $phase4_line_end){ $progress += $phase4_weight } else{ $progress += $phase4_weight * (($line_count - $phase3_line_end) / ($phase4_line_end - $phase3_line_end)) $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $progress $secondsRemaining = [int]($Est_TimeRemaining - $ElaspedTime.TotalSeconds) return [PSCustomObject]@{ Progress = [math]::Round($progress,2) Phase = "Phase 4" ElaspedTime = $ElaspedTime EST_TimeReamining = New-TimeSpan -Seconds $secondsRemaining PlotId = $plotId } } if ($line_count -lt $copyfile_line_end){ $Est_TimeRemaining = ($ElaspedTime.TotalSeconds * 100) / $progress $secondsRemaining = [int]($Est_TimeRemaining - $ElaspedTime.TotalSeconds) return [PSCustomObject]@{ Progress = [math]::Round($progress,2) Phase = "Copying" ElaspedTime = $ElaspedTime EST_TimeReamining = New-TimeSpan -Seconds $secondsRemaining PlotId = $plotId } } $progress += $copyphase_weight return [PSCustomObject]@{ Progress = [math]::Round($progress,2) Phase = "Completed" ElaspedTime = New-TimeSpan -Start $StartTime -End $LogItem.LastWriteTime EST_TimeReamining = 0 PlotId = $plotId } } function Get-ChiaPlottingStatistic { [CmdletBinding()] param( [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)] [string[]]$Path = (Get-ChildItem -Path $env:USERPROFILE\.chia\mainnet\plotter\ | sort CreationTime -Descending).FullName ) Process{ foreach ($log in $path){ if (Test-Path $log){ $Content = Get-Content -Path $log | Select-String "ID: ","Time for phase","Total time","Plot size","Buffer size","threads of stripe","Copy time","Copied final file from","Starting plotting progress into temporary dirs" | foreach {$_.ToString()} foreach ($line in $Content){ switch -Wildcard ($line){ "ID: *" {$PlotID = $line.Split(' ') | select -Skip 1 -First 1} "Plot size*" {$PlotSize = $line.split(' ') | select -Skip 3} #using select for these since indexing will error if empty "Buffer Size*" {$BufferSize = ($line.Split(' ') | select -Skip 3).split("M") | select -First 1} "*threads*" {$ThreadCount = $line.split(' ') | select -First 1 -Skip 1} "*phase 1*" {$Phase_1 = $line.Split(' ') | select -First 1 -Skip 5} "*phase 2*" {$Phase_2 = $line.Split(' ') | select -First 1 -Skip 5} "*phase 3*" {$Phase_3 = $line.Split(' ') | select -First 1 -Skip 5} "*phase 4*" {$phase_4 = $line.Split(' ') | select -First 1 -Skip 5} "Total time*" {$TotalTime = $line.Split(' ') | select -First 1 -Skip 3} "Copy time*" {$CopyTime = $line.Split(' ') | select -First 1 -Skip 3} "Starting plotting progress into temporary dirs*" {$TempDrive = ($line.Split(' ') | select -First 1 -Skip 6).Split('\') | select -First 1 } "Copied final file from*" {$FinalDrive = ($line.Split(' ') | select -First 1 -Skip 6).Split('\').Replace('"', '') | select -First 1} default {Write-Information "Could not match line: $line"} } } [PSCustomObject]@{ PSTypeName = "PSChiaPlotter.ChiaPlottingStatistic" PlotId = $PlotID KSize = $PlotSize "RAM(MiB)" = $BufferSize Threads = $ThreadCount "Phase_1_sec" = [int]$Phase_1 "Phase_2_sec" = [int]$Phase_2 "Phase_3_sec" = [int]$Phase_3 "Phase_4_sec" = [int]$phase_4 "TotalTime_sec" = [int]$TotalTime "CopyTime_sec" = [int]$CopyTime "PlotAndCopyTime_sec" = ([int]$CopyTime + [int]$TotalTime) "Time_Started" = (Get-Item -Path $log).CreationTime "Temp_drive" = $TempDrive "Final_drive" = $FinalDrive } Clear-Variable -Name "PlotID","PlotSize","BufferSize","ThreadCount","Phase_1","Phase_2","Phase_3","Phase_4","TotalTime","CopyTime","FinalDrive","TempDrive" -ErrorAction SilentlyContinue } } } } function Get-ChiaProcessCounter{ [CmdletBinding()] param( [Parameter(Mandatory)] [int[]]$ChiaPID ) foreach ($ID in $ChiaPID){ $QueryString += "OR IDProcess=$ID " } $Performance = Get-CimInstance -Query "Select workingSetPrivate,PercentProcessorTime,IDProcess FROM Win32_PerfFormattedData_PerfProc_Process WHERE NAME='_Total' $QueryString" $TotalCPU = $Performance | where {$_.Name -eq '_Total'} $ChiaProcesses = $Performance | where {$_.Name -ne '_Total'} foreach ($process in $ChiaProcesses){ if ($process.PercentProcessorTime -ne 0){ $CPUPer = ($process.PercentProcessorTime / $TotalCPU.PercentProcessorTime) * 100 $RoundedCPU = [math]::Round($CPUPer,2) } else{$CPUPer = 0} [PSCustomObject]@{ ChiaPID = $process.IDProcess CPUPercent = $RoundedCPU } } } function Get-ChiaRAMInfo { [CmdletBinding()] param( ) Begin{ } #Begin Process{ $Array = Get-CimInstance -Class Win32_PhysicalMemoryArray $CurrentRAM = Get-CimInstance -Class Win32_PhysicalMemory [PSCustomObject]@{ PSTypeName = "PSChiaPlotter.RAMInfo" ComputerName = $ENV:COMPUTERNAME SlotsInUse = ($CurrentRAM | Measure).Count SlotsFree = $Array.MemoryDevices - ($CurrentRAM | Measure).Count CurrentSize_GB = (($CurrentRAM).Capacity | Measure -Sum).Sum / 1gb MaxSize_GB = $Array.MaxCapacityEx / 1mb PartNumber = ($CurrentRAM.PartNumber | Select -Unique | foreach {$_.Trim()}) Manufacturer = ($CurrentRAM.Manufacturer | Select -Unique | foreach {$_.Trim()}) TotalSlots = $Array.MemoryDevices RAMDevices = $CurrentRAM } } #Process } function Show-ChiaPlottingStatistic{ [CmdletBinding()] param( [ValidateNotNullOrEmpty()] [string[]]$LogPath ) $LogStats = Get-ChiaPlottingStatistic -Path $Logpath $newlogstats = foreach ($stat in $LogStats){ try{ $phase1 = New-TimeSpan -Seconds $stat.Phase_1_sec $phase2 = New-TimeSpan -Seconds $stat.Phase_2_sec $phase3 = New-TimeSpan -Seconds $stat.Phase_3_sec $phase4 = New-TimeSpan -Seconds $stat.Phase_4_sec $totaltime = New-TimeSpan -Seconds $stat.TotalTime_sec $copyTime = New-TimeSpan -Seconds $stat.CopyTime_sec $copyandplot = New-TimeSpan -Seconds $stat.PlotAndCopyTime_sec if ($stat.PlotId){ $stat | Add-Member -NotePropertyMembers @{ Phase_1 = $phase1 Phase_2 = $phase2 Phase_3 = $phase3 Phase_4 = $phase4 PlotTime = $totaltime CopyPhase = $copyTime PlotAndCopy = $copyandplot } $stat } } catch{ Write-Information "Unable to add time span properties" } } if ($logPath.Count -eq 1){ $WPF = Join-Path -Path $MyInvocation.MyCommand.Module.ModuleBase -ChildPath "WPFWindows" $XAMLPath = Join-Path -Path $WPF -ChildPath ChiaLogStats.xaml $ChiaLogWindow = Import-Xaml -Path $XAMLPath $ChiaLogWindow.DataContext = $newlogstats $ChiaLogWindow.ShowDialog() | Out-Null } else{ $WPF = Join-Path -Path $MyInvocation.MyCommand.Module.ModuleBase -ChildPath "WPFWindows" $XAMLPath = Join-Path -Path $WPF -ChildPath ChiaLogStatsGrid.xaml $ChiaLogWindow = Import-Xaml -Path $XAMLPath $DataGrid = $ChiaLogWindow.FindName("DataGrid") $DataGrid.ItemsSource = $newlogstats $ChiaLogWindow.ShowDialog() | Out-Null } } function Show-PSChiaPlotter { param( [switch]$DebugWithNotepad ) Add-Type -AssemblyName PresentationFramework $PSChiaPlotterFolderPath = "$ENV:LOCALAPPDATA\PSChiaPlotter" if (-not(Test-Path -Path $PSChiaPlotterFolderPath)){ New-Item -Path $PSChiaPlotterFolderPath -ItemType Directory | Out-Null } $Global:UIHash = [hashtable]::Synchronized(@{}) $Global:DataHash = [hashtable]::Synchronized(@{}) $Global:ScriptsHash = [hashtable]::Synchronized(@{}) $InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() $UISync = [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new("UIHash", $UIHash, $Null) $DataSync = [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new("DataHash", $DataHash, $Null) $ScriptsSync = [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new("ScriptsHash", $ScriptsHash, $Null) $InitialSessionState.Variables.Add($UISync) $InitialSessionState.Variables.Add($DataSync) $InitialSessionState.Variables.Add($ScriptsSync) $RunspacePool = [runspacefactory]::CreateRunspacePool(1,10,$InitialSessionState,$Host) $RunspacePool.ApartmentState = "STA" $RunspacePool.ThreadOptions = "ReuseThread" $RunspacePool.open() #DataHash Adding Properties $DataHash.ModuleRoot = $MyInvocation.MyCommand.Module.ModuleBase $DataHash.PrivateFunctions = Join-Path -Path $DataHash.ModuleRoot -ChildPath "Private" $DataHash.LogPath = Join-Path $PSChiaPlotterFolderPath -ChildPath "PSChiaPlotterDebug.log" #$DataHash.Assemblies = Join-Path -Path $DataHash.ModuleRoot -ChildPath "Assemblies" $DataHash.WPF = Join-Path -Path $DataHash.ModuleRoot -ChildPath "WPFWindows" $DataHash.Classes = Join-Path -Path $DataHash.ModuleRoot -ChildPath "Classes" $DataHash.Runspaces = New-Object System.Collections.Generic.List[System.Object] #DEBUG SWITCH $DataHash.Debug = $DebugWithNotepad.IsPresent $ScriptsHash.RunspacePool = $RunspacePool #Import required assemblies and private functions Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName} #Get-childItem -Path $DataHash.Assemblies -File | ForEach-Object {Add-Type -Path $_.FullName} #$QueueRunspace = New-ChiaQueueRunspace #$QueueRunspace.Runspacepool = $RunspacePool #$ScriptsHash.QueueRunspace = $QueueRunspace #Create UI Thread $UIRunspace = New-UIRunspace $UIRunspace.RunspacePool = $RunspacePool $DataHash.UIRunspace = $UIRunspace $DataHash.UIHandle = $UIRunspace.BeginInvoke() $RunspacePoolEvent = Register-ObjectEvent -InputObject $DataHash.UIRunspace -EventName InvocationStateChanged -Action { $NewState = $Event.Sender.InvocationStateInfo.State if ($NewState -eq "Completed"){ try{ $ScriptsHash.RunspacePool.Close() $ScriptsHash.RunspacePool.Dispose() } catch{ #write log maybe } } else{ #do nothing } } } function Start-ChiaHarvesterWatcher { [CmdletBinding()] param( [string]$DebugLogFilePath = (Get-ChildItem -Path "$ENV:USERPROFILE\.chia\mainnet\log" -filter "debug.log").FullName, [ValidateRange(1,1000)] [int]$Sensitivity = 1, [Parameter()] [ValidateScript({[System.IO.Directory]::Exists((Split-Path -Path $_ -Parent))})] [string]$ExportCSVPath ) if ($PSBoundParameters.ContainsKey("ExportCSVPath")){ if (-not($ExportCSVPath.EndsWith('.csv'))){ Write-Warning "Export CSV Path does not end with .csv, please provide a valid CSV path and run the command again... exiting." return } } $chiaharvesterlog = "([0-9:.\-T]*) harvester chia.harvester.harvester: INFO\s*([0-9]*) plots were eligible for farming ([a-z0-9.]*) Found ([0-9]*) proofs. Time: ([0-9.]*) s. Total ([0-9]*) plots" $BestSpeed = 1000 $WorstSpeed = 0 $Over1Seconds = 0 $Over5Seconds = 0 $Over30Seconds = 0 $TotalAttempts = 0 $TotalFilterRatio = 0 $TotalLookupTime = 0 $proofsFound = 0 Get-Content -Path $DebugLogFilePath -Wait | foreach-object { switch -Regex ($_){ $chiaharvesterlog { $harvesterActivity = [pscustomobject]@{ Time = [datetime]::parse($Matches[1]) EligiblePlots = $Matches[2] LookUpTime = [double]$Matches[5] ProofsFound = $Matches[4] TotalPlots = $Matches[6] FilterRatio = $Matches[2] / $Matches[6] } $TotalAttempts++ switch ($harvesterActivity.LookUpTime) { {$_ -lt $BestSpeed} {$BestSpeed = $_} {$_ -gt $WorstSpeed} {$WorstSpeed = $_} {$_ -ge 1} {$Over1Seconds++} {$_ -ge 5} {$Over5Seconds++} {$_ -ge 30} {$Over30Seconds++} } if ($PSBoundParameters.ContainsKey("ExportCSVPath")){ $harvesterActivity | Export-Csv -Path $ExportCSVPath -Append } $proofsFound += $harvesterActivity.ProofsFound $TotalLookupTime += $harvesterActivity.LookUpTime $AverageSpeed = [math]::Round(($TotalLookupTime / $TotalAttempts),5) $TotalFilterRatio += $harvesterActivity.FilterRatio $newRatio = [math]::Round(($TotalFilterRatio / $TotalAttempts),5) $RGB = [math]::Round((255 * $harvesterActivity.LookUpTime * $Sensitivity) / 5) $eligibleplots = " " if ($harvesterActivity.EligiblePlots -gt 0){ $eligibleplots = $harvesterActivity.EligiblePlots } $host.UI.RawUI.WindowTitle = "Total Attempts: $TotalAttempts || LookUp Time - Best: $BestSpeed, Worst: $WorstSpeed, Avg: $AverageSpeed || Over 1 Sec:$Over1Seconds, Over 5 Sec: $Over5Seconds, Over 30 Sec: $Over30Seconds || FilterRatio: $newRatio || Proofs Found: $proofsFound || RGB: $RGB" Write-RGBText -Text "$eligibleplots|" -bRed ([math]::Min($RGB,255)) -bGreen ([math]::max([math]::Min(255,(510 - $RGB)),0)) -NoNewLine -UnderLine } } #switch } #foreach } function Start-ChiaParallelPlotting { param( [ValidateRange(1,128)] [int]$ParallelCount = 1, [ValidateRange(0,[int]::MaxValue)] [Alias("Delay")] [int]$DelayInSeconds = 3600, [int]$PlotsPerQueue = 1, [ValidateRange(3390,[int]::MaxValue)] [int]$Buffer = 3390, [ValidateRange(1,128)] [int]$Threads = 2, [Parameter(Mandatory)] [ValidateScript({[System.IO.Directory]::Exists($_)})] [string]$TempDirectoryPath, [Parameter(Mandatory)] [ValidateScript({[System.IO.Directory]::Exists($_)})] [string]$FinalDirectoryPath, [ValidateScript({[System.IO.Directory]::Exists($_)})] [string]$LogDirectoryPath = "$ENV:USERPROFILE\.chia\mainnet\plotter", [Parameter()] [string]$FarmerPublicKey, [Parameter()] [string]$PoolPublicKey, [Parameter()] [ValidateRange(1,[int]::MaxValue)] [int]$Buckets, [Parameter()] [ValidateScript({[System.IO.Directory]::Exists($_)})] [string]$SecondTempDirectoryPath, [switch]$DisableBitfield, [switch]$ExcludeFinalDirectory, [switch]$NoExit, [ValidateNotNullOrEmpty()] [string]$WindowTitle ) $AdditionalParameters = "" if ($PSBoundParameters.ContainsKey("WindowTitle")){ $AdditionalParameters += " -WindowTitle $WindowTitle" } if ($PSBoundParameters.ContainsKey("FarmerPublicKey")){ $AdditionalParameters += " -FarmerPublicKey $FarmerPublicKey" } if ($PSBoundParameters.ContainsKey("PoolPublicKey")){ $AdditionalParameters += " -PoolPublicKey $PoolPublicKey" } if ($PSBoundParameters.ContainsKey("Buckets")){ $AdditionalParameters += " -Buckets $Buckets" } if ($PSBoundParameters.ContainsKey("SecondTempDirectoryPath")){ $AdditionalParameters += " -SecondTempDirectoryPath '$SecondTempDirectoryPath'" } if ($DisableBitfield){ $AdditionalParameters += " -DisableBitfield" } if ($ExcludeFinalDirectory){ $AdditionalParameters += " -ExcludeFinalDirectory" } for ($Queue = 1; $Queue -le $ParallelCount;$Queue++){ if ($NoExit){ $NoExitFlag = "-NoExit" } $ChiaArguments = "-TotalPlots $plotsperQueue -Buffer $Buffer -Threads $Threads -TempDirectoryPath '$TempDirectoryPath' -FinalDirectoryPath '$FinalDirectoryPath' -LogDirectoryPath '$LogDirectoryPath' -QueueName Queue_$Queue $AdditionalParameters" $processParam = @{ FilePath = "powershell.exe" ArgumentList = "$NoExitFlag -Command Start-ChiaPlotting $ChiaArguments" } Start-Process @processParam if ($Queue -lt $ParallelCount){ Start-Sleep -Seconds $DelayInSeconds } } #for } function Start-ChiaPlotting { [CmdletBinding()] param( [ValidateRange(32,35)] [int]$KSize = 32, [ValidateRange(1,5000)] [int]$TotalPlots = 1, [int]$Buffer, [ValidateRange(1,256)] [int]$Threads = 2, [switch]$DisableBitfield, [switch]$ExcludeFinalDirectory, [Parameter(Mandatory)] [ValidateScript({[System.IO.Directory]::Exists($_)})] [string]$TempDirectoryPath, [Parameter()] [ValidateScript({[System.IO.Directory]::Exists($_)})] [string]$SecondTempDirectoryPath, [Parameter(Mandatory)] [ValidateScript({[System.IO.Directory]::Exists($_)})] [string]$FinalDirectoryPath, [Parameter()] [string]$FarmerPublicKey, [Parameter()] [string]$PoolPublicKey, [Parameter()] [ValidateRange(1,[int]::MaxValue)] [int]$Buckets, [ValidateScript({[System.IO.Directory]::Exists($_)})] [string]$LogDirectoryPath = "$ENV:USERPROFILE\.chia\mainnet\plotter", [switch]$NewWindow, [string]$QueueName = "Default_Queue", [string]$WindowTitle ) if (-not$PSBoundParameters.ContainsKey("Buffer")){ switch ($KSize){ 32 {$Buffer = 3390} 33 {$Buffer = 7400} 34 {$Buffer = 14800} 35 {$Buffer = 29600} } Write-Information "Buffer set to: $Buffer" } if ($PSBoundParameters.ContainsKey("WindowTitle")){ $WindowTitle = $WindowTitle + " |" } $E = if ($DisableBitfield){"-e"} $X = if ($ExcludeFinalDirectory){"-x"} #remove any trailing '\' since chia.exe hates them $TempDirectoryPath = $TempDirectoryPath.TrimEnd('\') $FinalDirectoryPath = $FinalDirectoryPath.TrimEnd('\') #path to chia.exe $ChiaPath = (Get-Item -Path "$ENV:LOCALAPPDATA\chia-blockchain\app-*\resources\app.asar.unpacked\daemon\chia.exe").FullName $ChiaArguments = "plots create -k $KSize -b $Buffer -r $Threads -t `"$TempDirectoryPath`" -d `"$FinalDirectoryPath`" $E $X" if ($PSBoundParameters.ContainsKey("SecondTempDirectoryPath")){ $SecondTempDirectoryPath = $SecondTempDirectoryPath.TrimEnd('\') $ChiaArguments += " -2 $SecondTempDirectoryPath" Write-Information "Added 2nd Temp Dir to Chia ArguementList" } if ($PSBoundParameters.ContainsKey("FarmerPublicKey")){ $ChiaArguments += " -f $FarmerPublicKey" } if ($PSBoundParameters.ContainsKey("PoolPublicKey")){ $ChiaArguments += " -p $PoolPublicKey" } if ($PSBoundParameters.ContainsKey("Buckets")){ $ChiaArguments += " -u $Buckets" } if ($ChiaPath){ Write-Information "Chia path exists, starting the plotting process" if (!$NewWindow){ for ($plotNumber = 1;$plotNumber -le $TotalPlots;$plotNumber++){ try{ $LogPath = Join-Path $LogDirectoryPath ((Get-Date -Format yyyy_MM_dd_hh-mm-ss-tt_) + "plotlog-" + $plotNumber + ".log") $PlottingParam = @{ FilePath = $ChiaPath ArgumentList = $ChiaArguments RedirectStandardOutput = $LogPath NoNewWindow = $true } $chiaProcess = Start-Process @PlottingParam -PassThru $host.ui.RawUI.WindowTitle = "$WindowTitle $QueueName - Plot $plotNumber out of $TotalPlots | Chia Process Id - $($chiaProcess.id)" #Have noticed that giving the process a second to start before checking the logs works better Start-Sleep 1 while (!$chiaProcess.HasExited){ try{ $progress = Get-ChiaPlotProgress -LogPath $LogPath -ErrorAction Stop $plotid = $progress.PlotId #write-progress will fail if secondsremaining is less than 0... $secondsRemaining = $progress.EST_TimeReamining.TotalSeconds if ($progress.EST_TimeReamining.TotalSeconds -le 0){ $secondsRemaining = 0 } Write-Progress -Activity "Queue $($QueueName): Plot $plotNumber out of $TotalPlots" -Status "$($progress.phase) - $($progress.Progress)%" -PercentComplete $progress.progress -SecondsRemaining $secondsRemaining Start-Sleep 5 } catch{ Write-Progress -Activity "Queue $($QueueName): Plot $plotNumber out of $TotalPlots" -Status "WARNING! PROGRESS UPDATES HAS FAILED! $($progress.phase) - $($progress.Progress)%" -PercentComplete $progress.progress -SecondsRemaining $secondsRemaining Start-Sleep 30 } } #while if ($chiaProcess.ExitCode -ne 0){ Get-ChildItem -Path $TempDirectoryPath -Filter "*$plotid*.tmp" | Remove-Item -Force } } catch{ $PSCmdlet.WriteError($_) } } #for } #if noNewWindow else{ $ChiaArguments += " -n $TotalPlots" $PlottingParam = @{ FilePath = $ChiaPath ArgumentList = $ChiaArguments RedirectStandardOutput = $LogPath } $PlottingProcess = Start-Process @PlottingParam -PassThru [PSCustomObject]@{ KSize = $KSize Buffer = $Buffer Threads = $Threads PID = $PlottingProcess.Id StartTime = $PlottingProcess.StartTime TempDir = $TempDirectoryPath FinalDir = $FinalDirectoryPath TempDir2 = $SecondTempDirectoryPath LogPath = $LogPath TotalPlotCount = $TotalPlots BitfieldEnabled = !$DisableBitfield.IsPresent ExcludeFinalDir = $ExcludeFinalDirectory.IsPresent } Write-Information "Plotting started, PID = $PID" } # else } #if chia path exits } function Get-BestChiaFinalDrive { [CmdletBinding()] param( $ChiaVolumes ) $finalplotsize = 101.4 * 1gb foreach ($finalvol in $ChiaVolumes){ $newVolumeInfo = Get-Volume -DriveLetter $finalvol.DriveLetter $finalvol.FreeSpace = $newVolumeInfo.SizeRemaining } $sortedVolumes = $ChiaVolumes | sort -Property FreeSpace -Descending foreach ($volume in $sortedVolumes){ $MasterVolume = $DataHash.MainViewModel.AllVolumes | where DriveLetter -eq $volume.DriveLetter if (($volume.FreeSpace - ($MasterVolume.PendingFinalRuns.Count * $finalplotsize)) -gt $finalplotsize){ return $volume } } } function Get-BestChiaTempDrive { [CmdletBinding()] param( $ChiaVolumes ) $requiredTempSize = 239 * 1gb $finalplotsize = 101.4 * 1gb foreach ($tempvol in $ChiaVolumes){ $newVolumeInfo = Get-Volume -DriveLetter $tempvol.DriveLetter $tempvol.FreeSpace = $newVolumeInfo.SizeRemaining } $sortedVolumes = $ChiaVolumes | sort -Property FreeSpace -Descending foreach ($volume in $sortedVolumes){ $MasterVolume = $DataHash.MainViewModel.AllVolumes | where DriveLetter -eq $volume.DriveLetter if ($MasterVolume.CurrentChiaRuns.Count -lt $volume.MaxConCurrentTempChiaRuns){ if (($volume.FreeSpace - ($MasterVolume.PendingFinalRuns.Count * $finalplotsize)) -gt $requiredTempSize){ return $volume } } } } function Get-ChiaTempSize{ [CmdletBinding()] param( $DirectoryPath, $PlotId ) try{ if ($PlotId -ne $null){ $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-CimInstance -Namespace "ROOT/Microsoft/Windows/Storage" -ClassName MSFT_Volume | Where {$_.DriveLetter -ne $Null} | sort -Property DriveLetter $AllPartitions = Get-Partition $AllDisks = Get-Disk $AllphysicalDisk = Get-PhysicalDisk foreach ($volume in $AllVolumes){ try{ $partition = $AllPartitions | where DriveLetter -eq $volume.DriveLetter $disk = $AllDisks | where DiskNumber -eq $partition.DiskNumber $physicalDisk = $AllphysicalDisk | where DeviceId -eq $disk.DiskNumber $Label = $volume.FileSystemLabel if ([string]::IsNullOrEmpty($volume.FileSystemLabel)){ $Label = "N/A" } if ($physicalDisk){ $ChiaVolume = [PSChiaPlotter.ChiaVolume]::new($volume.DriveLetter,$Label,$volume.Size,$volume.SizeRemaining) $ChiaVolume.BusType = $physicalDisk.BusType $ChiaVolume.MediaType = $physicalDisk.MediaType $MaxTempCount = [math]::Floor([decimal]($volume.size / (239 * 1gb))) $ChiaVolume.MaxConCurrentTempChiaRuns = $MaxTempCount $ChiaVolume Clear-Variable PhysicalDisk,Disk,Partition,MaxTempCount } } catch{ Write-PSChiaPlotterLog -LogType "Error" -LineNumber $_.InvocationInfo.ScriptLineNumber -Message $_.Exception.Message -DebugLogPath $DataHash.LogPath Write-Warning "Unable to create a ChiaVolume from driveletter $($volume.DriveLetter)" } } #volume } function Get-MaxKSize { [CmdletBinding()] param( [ValidateSet("K32","K33","K34","K35")] [string[]]$KSize = ("K32","K33","K34","K35"), [Parameter(Mandatory)] [int64]$TotalBytes ) foreach ($size in $KSize){ [MaximizedKSize]::new($size,$TotalBytes) } #foreach } function Get-OptimizedKSizePlotNumbers { [CmdletBinding()] param( [MaximizedKSize[]]$MaximizedKSize ) foreach ($size in $MaximizedKSize){ switch ($size.KSize){ "K32" { [OptimizedKPlots]::new(0,0,0,$Size.TotalBytes) } "K33" { for ($K33Count = 1; $K33Count -le $size.MaxPlots; $K33Count++){ [OptimizedKPlots]::new(0,0,$K33Count,$Size.TotalBytes) } #for } "K34" { for ($K34Count = 1; $K34Count -le $size.maxplots; $K34Count++){ [OptimizedKPlots]::new(0,$K34Count,0,$Size.TotalBytes) $k34sizeremaining = $Size.TotalBytes - ($K34Count * $size.KSizeBytes) $K33Max = Get-MaxKSize -TotalBytes $k34sizeremaining -KSize "K33" for ($k33 = 1; $k33 -le $k33max.MaxPlots; $k33++){ [OptimizedKPlots]::new(0,$K34Count,$k33,$Size.TotalBytes) } #for 33 } #for 34 } #34 "K35" { for ($k35count = 1; $k35count -le $size.maxplots; $k35count++){ [OptimizedKPlots]::new($k35count,0,0,$Size.TotalBytes) $k35sizeremaining = $Size.TotalBytes - ($k35count * $size.KSizeBytes) $k33max = Get-MaxKSize -Totalbytes $k35sizeremaining -KSize "K33" for ($k33 = 1; $k33 -le $k33max.MaxPlots; $k33++){ [OptimizedKPlots]::new($k35count,0,$k33,$Size.TotalBytes) } #for 33 $k34max = Get-MaxKSize -Totalbytes $k35sizeremaining -KSize "K34" for ($k34 = 1; $k34 -le $k34max.maxplots; $k34++){ [OptimizedKPlots]::new($k35count,$k34,0,$Size.TotalBytes) $sizeremaining = $Size.TotalBytes - (($k35count * $size.KSizeBytes) + ($k34 * $k34max.KSizeBytes)) $K33max = Get-MaxKSize -TotalBytes $sizeremaining -KSize "K33" for ($k33 = 1;$k33 -le $k33max.maxplots; $k33++){ [OptimizedKPlots]::new($k35count,$k34,$k33,$Size.TotalBytes) } } } } } #switch } #foreach } function Import-Xaml { param( $PathToXAML ) Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName System.Windows.Forms [xml]$xaml = Get-Content -Path $PathToXAML $manager = [System.Xml.XmlNamespaceManager]::new($xaml.NameTable) $manager.AddNamespace("x","http://schemas.microsoft.com/winfx/2006/xaml") $xamlReader = [System.Xml.XmlNodeReader]::new($xaml) [Windows.Markup.XamlReader]::Load($xamlReader) } function New-ChiaJobRunspace{ param( [Parameter(Mandatory)] $Job ) [powershell]::Create().AddScript{ Param ( $Job ) $ErrorActionPreference = "Stop" Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName System.Windows.Forms #Import required assemblies and private functions Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName} Get-childItem -Path $DataHash.Classes -File | ForEach-Object {Import-Module $_.FullName} for ($queue = 1; $queue -le $Job.QueueCount; $queue++){ try{ $newQueue = [PSChiaPlotter.ChiaQueue]::new($Job.JobNumber,$queue,$job.InitialChiaParameters) $newQueue.Status = "Waiting" $DataHash.MainViewModel.AllQueues.Add($newQueue) $Job.Queues.Add($newQueue) } catch{ Show-Messagebox -Text $_.Exception.Message -Title "Job $($Job.JobNumber) - Runspace" } } try{ for ($queue = 0;$queue -lt $Job.QueueCount;$queue++){ if ($queue -eq 0){ sleep -Seconds ($Job.FirstDelay * 60) $Job.Status = "Running" } #$Job.Queues[$queue].StartTime = [DateTime]::Now $Job.Queues[$queue].Status = "Running" $QueueRunspace = New-ChiaQueueRunspace -Queue $Job.Queues[$queue] -Job $Job $QueueRunspace.Runspacepool = $ScriptsHash.Runspacepool [void]$QueueRunspace.BeginInvoke() $DataHash.Runspaces.Add($QueueRunspace) if (($queue + 1) -ne $Job.QueueCount){ #plus 10 seconds for a min delay for data syncing insurance Start-Sleep -Seconds ($Job.DelayInMinutes * 60 + 10) } } } catch{ Show-Messagebox -Text $_.Exception.Message -Title "Job $($Job.JobNumber) - Runspace" | Out-Null } }.AddParameters($PSBoundParameters) } function New-ChiaQueueRunspace { param( [Parameter(Mandatory)] $Queue, $Job ) [powershell]::Create().AddScript{ Param ( $Job, $Queue ) $ErrorActionPreference = "Stop" Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName System.Windows.Forms #Import required assemblies and private functions Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName} Get-childItem -Path $DataHash.Classes -File | ForEach-Object {Import-Module $_.FullName} try{ for ($runNumber = 1;($Job.CompletedRunCount + $Job.RunsInProgress.Count) -lt $Job.TotalPlotCount;$runNumber++){ $ChiaProcess = $Null if ($Queue.Pause){ $Queue.Status = "Paused" while ($Queue.Pause){ sleep 10 } if (($Job.CompletedRunCount + $Job.RunsInProgress.Count) -ge $Job.TotalPlotCount){ break } } #grab a volume that has enough space Do { $TempVolume = Get-BestChiaTempDrive $Job.TempVolumes $FinalVolume = Get-BestChiaFinalDrive $Job.FinalVolumes if ($TempVolume -eq $Null){ $Queue.Status = "Waiting on Temp Space" Start-Sleep -Seconds 60 } elseif ($FinalVolume -eq $Null){ $Queue.Status = "Waiting on Final Dir Space" Start-Sleep -Seconds 60 } } while ($TempVolume -eq $null -or $FinalVolume -eq $null) if (($Job.CompletedRunCount + $Job.RunsInProgress.Count) -ge $Job.TotalPlotCount){ break } $Queue.Status = "Running" $plottingParameters = [PSChiaPlotter.ChiaParameters]::New($Queue.PlottingParameters) $plottingParameters.TempVolume = $TempVolume $plottingParameters.FinalVolume = $FinalVolume $newRun = [PSChiaPlotter.ChiaRun]::new($job.JobNumber,$Queue.QueueNumber,$runNumber,$plottingParameters) if ($DataHash.Debug){ Start-GUIDebugRun -ChiaRun $newRun -ChiaQueue $Queue -ChiaJob $Job } else{ #Show-Object $newRun Start-GUIChiaPlotting -ChiaRun $newRun -ChiaQueue $Queue -ChiaJob $Job } #sleep to give some time for updating sleep 2 } $Queue.Status = "Finished" } catch{ Show-Messagebox -Text $_.Exception.Message -Title "Queue - $($Queue.QueueNumber)" | Out-Null if ($ChiaProcess){ Show-Messagebox -Text "The Following Chia Process may be running and might need to killed - PID $($ChiaProcess.Id)" -Title "Queue" | Out-Null } } }.AddParameters($PSBoundParameters) } function New-UIRunspace{ [powershell]::Create().AddScript{ $ErrorActionPreference = "Stop" Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName System.Windows.Forms #[System.Windows.Forms.MessageBox]::Show("Hello") #Import required assemblies and private functions Try{ Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName} Get-childItem -Path $DataHash.Classes -File | ForEach-Object {Import-Module $_.FullName} #Get-childItem -Path $DataHash.Assemblies -File | ForEach-Object {Add-Type -Path $_.FullName} $XAMLPath = Join-Path -Path $DataHash.WPF -ChildPath MainWindow.xaml $MainWindow = Import-Xaml -Path $XAMLPath #Assign GUI Controls To Variables $UIHash.MainWindow = $MainWindow $UIHash.Jobs_DataGrid = $MainWindow.FindName("Jobs_DataGrid") $UIHash.Queues_DataGrid = $MainWindow.FindName("Queues_DataGrid") $UIHash.Runs_DataGrid = $MainWindow.FindName("Runs_DataGrid") $UIHash.CompletedRuns_DataGrid = $MainWindow.FindName("CompletedRuns_DataGrid") $UIHash.Refreshdrives_Button = $MainWindow.FindName("RefreshdrivesButton") $DataHash.RefreshingDrives = $false $UIHash.NewJob_Button = $MainWindow.FindName("AddJob_Button") $DataHash.MainViewModel = [PSChiaPlotter.MainViewModel]::new() $UIHash.MainWindow.DataContext = $DataHash.MainViewModel #Add Master Copy of volumes to MainViewModel these are used to keep track of # all jobs that are running on the drives Get-ChiaVolume | foreach { $DataHash.MainViewModel.AllVolumes.Add($_) } #ButtonClick $UIHash.NewJob_Button.add_Click({ try{ #Get-childItem -Path $DataHash.Classes -File | ForEach-Object {Import-Module $_.FullName} $XAMLPath = Join-Path -Path $DataHash.WPF -ChildPath NewJobWindow.xaml $UIHash.NewJob_Window = Import-Xaml -Path $XAMLPath $jobNumber = $DataHash.MainViewModel.AllJobs.Count + 1 $newJob = [PSChiaPlotter.ChiaJob]::new() $newJob.JobNumber = $jobNumber $NewJobViewModel = [PSChiaPlotter.NewJobViewModel]::new($newJob) #need to run get-chiavolume twice or the temp and final drives will be the same object in the application and will update each other... Get-ChiaVolume | foreach { $NewJobViewModel.TempAvailableVolumes.Add($_) } Get-ChiaVolume | foreach { $NewJobViewModel.FinalAvailableVolumes.Add($_) } $newJob.Status = "Waiting" $UIHash.NewJob_Window.DataContext = $NewJobViewModel $CreateJob_Button = $UIHash.NewJob_Window.FindName("CreateJob_Button") $CreateJob_Button.add_Click({ try{ $Results = Test-ChiaParameters $newJob if ($NewJob.DelayInMinutes -eq 60){ $response = Show-Messagebox -Text "You left the default delay time of 60 Minutes, continue?" -Button YesNo if ($response -eq [System.Windows.MessageBoxResult]::No){ return } } if ($Results -ne $true){ Show-Messagebox -Text $Results -Title "Invalid Parameters" -Icon Warning return } $DataHash.MainViewModel.AllJobs.Add($newJob) $newJobRunSpace = New-ChiaJobRunspace -Job $newJob $newJobRunSpace.Runspacepool = $ScriptsHash.RunspacePool [void]$newJobRunSpace.BeginInvoke() $DataHash.Runspaces.Add($newJobRunSpace) $UIHash.NewJob_Window.Close() } catch{ Show-Messagebox -Text $_.Exception.Message -Title "Create New Job Error" -Icon Error } }) $CancelJobCreation_Button = $UIHash.NewJob_Window.FindName("CancelJobCreation_Button") $CancelJobCreation_Button.Add_Click({ try{ $UIHash.NewJob_Window.Close() } catch{ Show-Messagebox -Text $_.Exception.Message -Title "Exit New Job Window Error" -Icon Error } }) $UIHash.NewJob_Window.ShowDialog() } catch{ Show-Messagebox -Text $_.Exception.Message -Title "Create New Job Error" -Icon Error } }) $UIHash.Refreshdrives_Button.Add_Click({ try{ if ($DataHash.RefreshingDrives){ Show-Messagebox -Text "A drive refresh is currently in progress" -Icon Information return } $DataHash.RefreshingDrives = $true Update-ChiaVolume -ErrorAction Stop $DataHash.RefreshingDrives = $false } catch{ $DataHash.RefreshingDrives = $false Show-Messagebox -Text $_.Exception.Message -Title "Refresh Drives" -Icon Error } }) #$ScriptsHash.QueueHandle = $ScriptsHash.QueueRunspace.BeginInvoke() $UIHash.MainWindow.add_Closing({ Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName} # end session and close runspace on window exit $DialogResult = Show-Messagebox -Text "Closing this window will end all Chia processes" -Title "Warning!" -Icon Warning -Buttons OKCancel if ($DialogResult -eq [System.Windows.MessageBoxResult]::Cancel) { $PSItem.Cancel = $true } else{ #$ScriptsHash.QueueHandle.EndInvoke($QueueHandle) Stop-PSChiaPlotter } }) #Hyperlink thingy $UIHash.MainWindow.add_PreviewMouseLeftButtonDown({ Get-childItem -Path $DataHash.PrivateFunctions -File | ForEach-Object {Import-Module $_.FullName} $grid = $UIHash.Runs_DataGrid $result = [System.Windows.Media.VisualTreeHelper]::HitTest($grid, $_.GetPosition($grid)) $element = $result.VisualHit if (($null -ne $element) -and ($element.GetType().Name -eq "TextBlock")) { if ($null -ne $element.Parent) { # handle hyperlink click if (($null -ne $element.Parent.Parent) -and ($element.Parent.Parent.GetType().Name -eq "Hyperlink")) { $hyperlink = $element.Parent.Parent if (Test-Path -LiteralPath $hyperlink.NavigateUri.OriginalString) { # launch file try{ Invoke-Item -LiteralPath $hyperlink.NavigateUri.OriginalString -ErrorAction Stop } catch{ Show-Messagebox -Message "$($_.ErrorDetails.Message)" -Title "Hyperlink Click Error" } } } } } }) $MainWindow.ShowDialog() } catch{ $Message = "$($_.Exception.Message)" $Message += "`nLine # -$($_.InvocationInfo.ScriptLineNumber )" $Message += "`nLine - $($_.InvocationInfo.Line)" Show-Messagebox -Text $Message -Title "UI Runspace Error" -Icon Error } } } function Show-Messagebox { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Text, [string]$Title = "Message Box", [System.Windows.MessageBoxButton]$Buttons =[System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]$Icon = [System.Windows.MessageBoxImage]::None ) [System.Windows.MessageBox]::Show($Text,$Title,$Buttons,$Icon) } function Start-GUIChiaPlotting { [CmdletBinding()] param( #[string]$SecondTempDirecoryPath, #$FarmerPublicKey, #$PoolPublicKey, $ChiaRun, $ChiaQueue, $ChiaJob ) #not really needed, but just wanted to make each parameter its own variable $PlottingParameters = $ChiaRun.PlottingParameters $KSize = $PlottingParameters.KSize $Buffer = $PlottingParameters.RAM $Threads = $PlottingParameters.Threads $DisableBitfield = $PlottingParameters.DisableBitField $ExcludeFinalDirectory = $PlottingParameters.ExcludeFinalDirectory $TempDirectoryPath = $PlottingParameters.TempVolume.DirectoryPath $FinalDirectoryPath = $PlottingParameters.FinalVolume.DirectoryPath $LogDirectoryPath = $PlottingParameters.LogDirectory #$SecondTempDirecoryPath = $PlottingParameters.TempVolume.DirectoryPath $PoolPublicKey = $PlottingParameters.PoolPublicKey $FarmerPublicKey = $PlottingParameters.FarmerPublicKey $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 (-not[string]::IsNullOrWhiteSpace($PoolPublicKey)){ $ChiaArguments += " -p $PoolPublicKey" } if (-not[string]::IsNullOrWhiteSpace($FarmerPublicKey)){ $ChiaArguments += " -f $FarmerPublicKey" } if ($ChiaPath){ Write-Information "Chia path exists, starting the plotting process" try{ $LogPath = Join-Path $LogDirectoryPath ((Get-Date -Format yyyy_MM_dd_hh-mm-ss-tt_) + "plotlog-" + $ChiaQueue.QueueNumber + "-" + $ChiaRun.RunNumber + ".log") $ChiaRun.LogPath = $LogPath $PlottingParam = @{ FilePath = $ChiaPath ArgumentList = $ChiaArguments RedirectStandardOutput = $LogPath } $chiaProcess = Start-Process @PlottingParam -PassThru -WindowStyle Hidden #this is 100% require for th exit code to be seen by powershell when redirectingstandardoutput $handle = $chiaProcess.Handle $ChiaRun.ChiaProcess = $ChiaProcess $ChiaRun.ProcessId = $ChiaProcess.Id $ChiaJob.RunsInProgress.Add($ChiaRun) $ChiaRun.PlottingParameters.TempVolume.CurrentChiaRuns.Add($ChiaRun) $TempMasterVolume = $DataHash.MainViewModel.AllVolumes | where DriveLetter -eq $ChiaRun.PlottingParameters.TempVolume.DriveLetter $TempMasterVolume.CurrentChiaRuns.Add($ChiaRun) $FinalMasterVolume = $DataHash.MainViewModel.AllVolumes | where DriveLetter -eq $ChiaRun.PlottingParameters.FinalVolume.DriveLetter $FinalMasterVolume.PendingFinalRuns.Add($ChiaRun) $ChiaQueue.CurrentRun = $ChiaRun $DataHash.MainViewModel.CurrentRuns.Add($ChiaRun) $DataHash.MainViewModel.AllRuns.Add($ChiaRun) #Have noticed that giving the process a second to start before checking the logs works better Start-Sleep 1 while (!$chiaProcess.HasExited){ try{ $progress = Get-ChiaPlotProgress -LogPath $LogPath -ErrorAction Stop $plotid = $progress.PlotId $ChiaRun.Progress = $progress.progress $ChiaQueue.CurrentTime = [DateTime]::Now $ChiaRun.CurrentTime = [DateTime]::Now $ChiaRun.Phase = $progress.Phase if ($progress.EST_TimeReamining.TotalSeconds -le 0){ $ChiaRun.EstTimeRemaining = New-TimeSpan -Seconds 0 } else{ $ChiaRun.EstTimeRemaining = $progress.EST_TimeReamining } $ChiaRun.EstTimeRemaining = $progress.EST_TimeReamining $ChiaRun.TempSize = Get-ChiaTempSize -DirectoryPath $TempDirectoryPath -PlotId $plotid Start-Sleep (5 + $ChiaQueue.QueueNumber) } catch{ Start-Sleep 30 } } #while $ChiaJob.RunsInProgress.Remove($ChiaRun) $ChiaJob.CompletedRunCount++ $FinalMasterVolume.PendingFinalRuns.Remove($ChiaRun) $TempMasterVolume.CurrentChiaRuns.Remove($ChiaRun) $ChiaRun.ExitCode = $ChiaRun.ChiaPRocess.ExitCode #if this is null then an error will occur if we try to set this property if ($ChiaRun.ExitTime){ $ChiaRun.ExitTime = $ChiaProcess.ExitTime } if ($ChiaRun.ChiaPRocess.ExitCode -ne 0){ $ChiaRun.Status = "Failed" $ChiaQueue.FailedPlotCount++ $ChiaJob.FailedPlotCount++ $DataHash.MainViewModel.FailedRuns.Add($ChiaRun) Get-ChildItem -Path $TempDirectoryPath -Filter "*$plotid*.tmp" | foreach { try{ Remove-Item -Path $_.FullName -Force -ErrorAction Stop } catch{ Show-Messagebox -Text $_.Exception.Message | Out-Null } } } else{ $ChiaRun.Status = "Completed" $ChiaJob.CompletedPlotCount++ $ChiaQueue.CompletedPlotCount++ $DataHash.MainViewModel.CompletedRuns.Add($ChiaRun) Update-ChiaGUISummary -Success } $DataHash.MainViewModel.CurrentRuns.Remove($ChiaRun) $ChiaRun.PlottingParameters.TempVolume.CurrentChiaRuns.Remove($ChiaRun) } catch{ if (-not$DataHash.MainViewModel.FailedRuns.Contains($ChiaRun)){ $DataHash.MainViewModel.FailedRuns.Add($ChiaRun) } if ($DataHash.MainViewModel.CurrentRuns.Contains($ChiaRun)){ $DataHash.MainViewModel.CurrentRuns.Remove($ChiaRun) } if ($ChiaJob.RunsInProgress.Contains($ChiaRun)){ $ChiaJob.RunsInProgress.Remove($ChiaRun) } if ($FinalMasterVolume){ if ($FinalMasterVolume.PendingFinalRuns.Contains($ChiaRun)){ $FinalMasterVolume.PendingFinalRuns.Remove($ChiaRun) } } $PSCmdlet.WriteError($_) } } #if chia path exits else{ $Message = "chia.exe was not found" $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( [System.IO.FileNotFoundException]::new($Message,"$ENV:LOCALAPPDATA\chia-blockchain\app-*\resources\app.asar.unpacked\daemon\chia.exe"), 'ChiaPathInvalid', [System.Management.Automation.ErrorCategory]::ObjectNotFound, "$ENV:LOCALAPPDATA\chia-blockchain\app-*\resources\app.asar.unpacked\daemon\chia.exe" ) $PSCmdlet.ThrowTerminatingError($ErrorRecord) $PSCmdlet.ThrowTerminatingError("Invalid Log Path Directory: $LogDirectoryPath") } } function Start-GUIDebugRun{ [CmdletBinding()] param( $ChiaRun, $ChiaQueue, $ChiaJob ) try{ $PlottingParameters = $ChiaRun.PlottingParameters $KSize = $PlottingParameters.KSize $Buffer = $PlottingParameters.RAM $Threads = $PlottingParameters.Threads $DisableBitfield = $PlottingParameters.DisableBitField $ExcludeFinalDirectory = $PlottingParameters.ExcludeFinalDirectory $TempDirectoryPath = $PlottingParameters.TempVolume.DirectoryPath $FinalDirectoryPath = $PlottingParameters.FinalVolume.DirectoryPath $LogDirectoryPath = $PlottingParameters.LogDirectory $SecondTempDirecoryPath = $PlottingParameters.TempVolume.DirectoryPath $E = if ($DisableBitfield){"-e"} $X = if ($ExcludeFinalDirectory){"-x"} if (Test-Path $LogDirectoryPath){ $LogPath = Join-Path $LogDirectoryPath ((Get-Date -Format yyyy_MM_dd_hh-mm-ss-tt_) + "plotlog" + ".log") } $ChiaProcess = start-process -filepath notepad.exe -PassThru -RedirectStandardOutput $LogPath $handle = $ChiaProcess.Handle $ChiaRun.ChiaProcess = $ChiaProcess $ChiaRun.ProcessId = $ChiaProcess.Id $DataHash.MainViewModel.AllRuns.Add($ChiaRun) $ChiaJob.RunsInProgress.Add($ChiaRun) $TempVolume.CurrentChiaRuns.Add($ChiaRun) $TempMasterVolume = $DataHash.MainViewModel.AllVolumes | where DriveLetter -eq $ChiaRun.PlottingParameters.TempVolume.DriveLetter $TempMasterVolume.CurrentChiaRuns.Add($ChiaRun) $FinalMasterVolume = $DataHash.MainViewModel.AllVolumes | where DriveLetter -eq $ChiaRun.PlottingParameters.FinalVolume.DriveLetter $FinalMasterVolume.PendingFinalRuns.Add($ChiaRun) $ChiaQueue.CurrentRun = $ChiaRun $DataHash.MainViewModel.CurrentRuns.Add($ChiaRun) while (-not$ChiaProcess.HasExited){ $ChiaQueue.CurrentTime = [DateTime]::Now $ChiaRun.CurrentTime = [DateTime]::Now $ChiaRun.Progress += 5 sleep (10 + $ChiaQueue.QueueNumber) } $TempMasterVolume.CurrentChiaRuns.Remove($ChiaRun) $FinalMasterVolume.PendingFinalRuns.Remove($ChiaRun) $ChiaJob.RunsInProgress.Remove($ChiaRun) $ChiaJob.CompletedRunCount++ $ChiaRun.ExitCode = $ChiaProcess.ExitCode if ($ChiaProcess.ExitTime -ne $null){ $ChiaRun.ExitTime = $ChiaProcess.ExitTime } $ChiaRun.ExitTime = $ChiaProcess.ExitTime if ($ChiaProcess.ExitCode -eq 0){ $ChiaRun.Status = "Completed" $ChiaJob.CompletedPlotCount++ $ChiaQueue.CompletedPlotCount++ $DataHash.MainViewModel.CompletedRuns.Add($ChiaRun) Update-ChiaGUISummary -Success } else{ $ChiaRun.Status = "Failed" $ChiaJob.FailedPlotCount++ $ChiaQueue.FailedPlotCount++ $DataHash.MainViewModel.FailedRuns.Add($ChiaRun) } $DataHash.MainViewModel.CurrentRuns.Remove($ChiaRun) } catch{ if (-not$DataHash.MainViewModel.FailedRuns.Contains($ChiaRun)){ $DataHash.MainViewModel.FailedRuns.Add($ChiaRun) } if ($DataHash.MainViewModel.CurrentRuns.Contains($ChiaRun)){ $DataHash.MainViewModel.CurrentRuns.Remove($ChiaRun) } if ($ChiaJob.RunsInProgress.Contains($ChiaRun)){ $ChiaJob.RunsInProgress.Remove($ChiaRun) } if ($FinalMasterVolume){ if ($FinalMasterVolume.PendingFinalRuns.Contains($ChiaRun)){ $FinalMasterVolume.PendingFinalRuns.Remove($ChiaRun) } } $PSCmdlet.WriteError($_) } } function Stop-PSChiaPlotter{ [CmdletBinding()] param() $RunningQueues = $DataHash.MainViewModel.AllQueues | Where-Object Status -eq "Running" foreach ($Queue in $RunningQueues){ $queue.Status = "Paused" } $ALLChiaProcesses = $DataHash.MainViewModel.CurrentRuns foreach ($run in $ALLChiaProcesses){ try{ Stop-Process $run.ProcessID } catch{ $logParam = @{ LogType = "Error" Message = $_.Exception.Message LineNumber = $_.InvocationInfo.ScriptLineNumber DebugLogPath = $DataHash.LogPath } Write-PSChiaPlotterLog @logParam } } $RunningRunspaces = $DataHash.Runspaces foreach ($runspace in $RunningRunspaces){ try{ $runspace.Stop() } catch{ $logParam = @{ LogType = "Error" Message = $_.Exception.Message LineNumber = $_.InvocationInfo.ScriptLineNumber DebugLogPath = $DataHash.LogPath } Write-PSChiaPlotterLog @logParam } } } function Test-ChiaParameters { param( $NewJob ) $ChiaParameters = $NewJob.InitialChiaParameters if ($ChiaParameters.RAM -lt 3390){ return "RAM needs to be greater than 3390" } if ($ChiaParameters.Threads -le 0){ return "Threads needs to 1 or higher" } 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" } if (-not$tempvol.DirectoryPath.StartsWith($tempvol.DriveLetter)){ return "Directory path '$($tempvol.DirectoryPath)' for Drive $($tempvol.DriveLetter) does not start with $($tempvol.DriveLetter)" } } 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" } if (-not$finalvol.DirectoryPath.StartsWith($finalvol.DriveLetter)){ return "Directory path '$($finalvol.DirectoryPath)' for Drive $($finalvol.DriveLetter) does not start with $($finalvol.DriveLetter)" } } if (-not[System.IO.Directory]::Exists($ChiaParameters.LogDirectory)){ return "Log Directory does not exists" } if ($NewJob.DelayInMinutes -gt 35791){ return "Delay Time is greater than 35791 minutes, which is the max" } if ($NewJob.FirstDelay -gt 35791){ return "First delay time is greater than 35791 minutes, which is the max" } return $true } function Update-ChiaGUISummary{ [CmdletBinding()] param( [switch]$Success, [switch]$Failed ) if ($Success){ $OneDayAgo = (Get-Date).AddDays(-1) $PlotsIn24Hrs = ($DataHash.MainViewModel.CompletedRuns | where ExitTime -GT $OneDayAgo | Measure-Object).Count $DataHash.MainViewModel.PlotPlottedPerDay = $PlotsIn24Hrs $DataHash.MainViewModel.TBPlottedPerDay = [math]::Round(($PlotsIn24Hrs * 101.4) / 1000,2) $SortedRuns = $DataHash.MainViewModel.CompletedRuns | Sort-Object -Property Runtime $Fastest = $SortedRuns | Select-Object -First 1 $Slowest = $SortedRuns | Select-Object -Last 1 $Average = $SortedRuns.RunTime | Measure-Object -Property TotalSeconds -Average if ($Fastest){ $DataHash.MainViewModel.FastestRun = $Fastest.Runtime } if ($Slowest){ $DataHash.MainViewModel.SlowestRun = $Slowest.Runtime } if ($Average){ $AverageRun = New-TimeSpan -Seconds $Average.Average $DataHash.MainViewModel.AverageTime = $AverageRun } } } function Update-ChiaVolume { [CmdletBinding()] param() $Volumes = Get-ChiaVolume $CurrentVolumes = $Volumes | where DriveLetter -in $DataHash.MainViewModel.AllVolumes.DriveLetter foreach ($volume in $CurrentVolumes){ $matchedVolume = $DataHash.MainViewModel.AllVolumes | where DriveLetter -eq $volume.DriveLetter if ($matchedVolume){ $matchedVolume.FreeSpace = $volume.FreeSpace $matchedVolume = $null } } $newVolumes = $Volumes | where DriveLetter -notin $DataHash.MainViewModel.AllVolumes.DriveLetter foreach ($newvolume in $newVolumes){ $DataHash.MainViewModel.AllVolumes.Add($newvolume) } $removedVolumes = $DataHash.MainViewModel.AllVolumes | where DriveLetter -NotIn $Volumes.DriveLetter foreach ($removedvolume in $removedVolumes){ $DataHash.MainViewModel.AllVolumes.Remove($removedvolume) } } function Write-PSChiaPlotterLog { [CmdletBinding()] param( [ValidateSet("INFO","Warning","ERROR")] [string]$LogType, [string]$Message, [int]$LineNumber, [string]$DebugLogPath ) try{ $Date = Get-Date -Format "[yyyy-MM-dd.HH:mm:ss]" $LogLine = "$Date-$LogType-$LineNumber-$Message" $LogLine | Out-File $DebugLogPath -Append } catch{ $PSCmdlet.WriteError($_) } } function Write-RGBText { [CmdletBinding()] param( [string]$Text, [Parameter(Position = 1)] [int]$fRed = 0, [int]$fGreen = 0, [int]$fBlue = 0, [int]$bRed = 0, [int]$bGreen = 0, [int]$bBlue = 0, # No newline after the text. [Parameter()] [switch] $NoNewLine, [switch]$UnderLine ) $escape = [char]27 + '[' $resetAttributes = "$($escape)0m" if ($UnderLine){ $UL = "$($escape)4m" } $foreground = "$($escape)38;2;$($fRed);$($fGreen);$($fBlue)m" $background = "$($escape)48;2;$($bRed);$($bGreen);$($bBlue)m" Write-Host ($foreground + $background + $UL + $Text + $resetAttributes) -NoNewline:$NoNewLine } Export-ModuleMember -function ConvertTo-FriendlyTimeSpan, Get-ChiaHarvesterActivity, Get-ChiaKPlotCombination, Get-ChiaMaxParallelCount, Get-ChiaPlotProgress, Get-ChiaPlottingStatistic, Get-ChiaProcessCounter, Get-ChiaRAMInfo, Show-ChiaPlottingStatistic, Show-PSChiaPlotter, Start-ChiaHarvesterWatcher, Start-ChiaParallelPlotting, Start-ChiaPlotting |