param( [Parameter(Mandatory = $false)] [Array]$Algorithm = $null, [Parameter(Mandatory = $false)] [Array]$PoolsName = $null, [Parameter(Mandatory = $false)] [array]$CoinsName = $null, [Parameter(Mandatory = $false)] [String]$MiningMode = $null, [Parameter(Mandatory = $false)] [array]$GroupNames = $null, [Parameter(Mandatory = $false)] [string]$PercentToSwitch = $null ) ##Parameters for testing, must be commented on real use # $MiningMode='Automatic' # $MiningMode='Manual' # $PoolsName=('ahashpool','miningpoolhub','hashrefinery') # $PoolsName='whattomine' # $PoolsName='zergpool' # $PoolsName='yiimp' # $PoolsName='ahashpool' # $PoolsName=('hashrefinery','zpool') # $PoolsName='miningpoolhub' # $PoolsName='zpool' # $PoolsName='hashrefinery' # $PoolsName='altminer' # $PoolsName='blazepool' # $PoolsName="Nicehash" # $PoolsName="Nanopool" # $CoinsName =('bitcore','Signatum','Zcash') # $CoinsName ='zcash' # $Algorithm =('phi','x17') # $GroupNames=('rx580') $error.clear() $ScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path . $ScriptRoot\Include.ps1 #Start log file $LogPath = "$ScriptRoot\Logs\" if (!(Test-Path -Path $LogPath)) { New-Item -Path $LogPath -ItemType directory | Out-Null } $LogName = $LogPath + "$(Get-Date -Format "yyyy-MM-dd_HH-mm-ss").log" Start-Transcript $LogName #for start log msg Stop-Transcript $LogFile = [System.IO.StreamWriter]::new( $LogName, $true ) $LogFile.AutoFlush = $true Clear-Files if ([Net.ServicePointManager]::SecurityProtocol -notmatch [Net.SecurityProtocolType]::Tls12) { [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12 } # Force Culture to en-US $culture = [System.Globalization.CultureInfo]::CreateSpecificCulture("en-US") $culture.NumberFormat.NumberDecimalSeparator = "." $culture.NumberFormat.NumberGroupSeparator = "," [System.Threading.Thread]::CurrentThread.CurrentCulture = $culture $ErrorActionPreference = "Continue" $Config = Get-Config $Application = "Forager" $Release = "18.12" Log-Message "$Application v$Release" $Host.UI.RawUI.WindowTitle = "$Application v$Release" $env:CUDA_DEVICE_ORDER = 'PCI_BUS_ID' #This align cuda id with nvidia-smi order $env:GPU_FORCE_64BIT_PTR = 0 #For AMD $env:GPU_MAX_HEAP_SIZE = 100 #For AMD $env:GPU_USE_SYNC_OBJECTS = 1 #For AMD $env:GPU_MAX_ALLOC_PERCENT = 100 #For AMD $env:GPU_SINGLE_ALLOC_PERCENT = 100 #For AMD $progressPreference = 'silentlyContinue' #No progress message on web requests #$progressPreference = 'Stop' Set-Location $ScriptRoot #Set process priority to BelowNormal to avoid hash rate drops on systems with weak CPUs (Get-Process -Id $PID).PriorityClass = "BelowNormal" Import-Module NetSecurity -ErrorAction SilentlyContinue Import-Module Defender -ErrorAction SilentlyContinue Import-Module "$env:Windir\System32\WindowsPowerShell\v1.0\Modules\NetSecurity\NetSecurity.psd1" -ErrorAction SilentlyContinue Import-Module "$env:Windir\System32\WindowsPowerShell\v1.0\Modules\Defender\Defender.psd1" -ErrorAction SilentlyContinue if (Get-Command "Unblock-File" -ErrorAction SilentlyContinue) {Get-ChildItem . -Recurse | Unblock-File} if ((Get-Command "Get-MpPreference" -ErrorAction SilentlyContinue) -and (Get-MpComputerStatus -ErrorAction SilentlyContinue) -and (Get-MpPreference).ExclusionPath -notcontains (Convert-Path .)) { Start-Process (@{desktop = "powershell"; core = "pwsh"}.$PSEdition) "-Command Import-Module '$env:Windir\System32\WindowsPowerShell\v1.0\Modules\Defender\Defender.psd1'; Add-MpPreference -ExclusionPath '$(Convert-Path .)'" -Verb runAs } $ActiveMiners = @() $ShowBestMinersOnly = $true $Interval = @{ Current = $null Last = $null Duration = $null StartTime = $null LastTime = $null Benchmark = $null } $Screen = $Config.StartScreen #---Parameters checking if (@('Manual', 'Automatic', 'Automatic24h') -notcontains $MiningMode) { Log-Message "Parameter MiningMode not valid, valid options: Manual, Automatic, Automatic24h" -Severity Warn Exit } $Params = @{ Querymode = "info" PoolsFilterList = $PoolsName CoinFilterList = $CoinsName Location = $Location AlgoFilterList = $Algorithm } $PoolsChecking = Get-Pools @Params $PoolsErrors = switch ($MiningMode) { "Automatic" {$PoolsChecking | Where-Object ActiveOnAutomaticMode -eq $false} "Automatic24h" {$PoolsChecking | Where-Object ActiveOnAutomatic24hMode -eq $false} "Manual" {$PoolsChecking | Where-Object ActiveOnManualMode -eq $false } } $PoolsErrors | ForEach-Object { Log-Message "Selected MiningMode is not valid for pool $($_.Name)" -Severity Warn Exit } if ($MiningMode -eq 'Manual' -and ($CoinsName -split ',').Count -ne 1) { Log-Message "On manual mode one coin must be selected" -Severity Warn Exit } if ($MiningMode -eq 'Manual' -and ($Algorithm -split ',').Count -ne 1) { Log-Message "On manual mode one algorithm must be selected" -Severity Warn Exit } #parameters backup $ParamAlgorithmBCK = $Algorithm $ParamPoolsNameBCK = $PoolsName $ParamCoinsNameBCK = $CoinsName $ParamMiningModeBCK = $MiningMode try {Set-WindowSize 180 50} catch {} $Interval.StartTime = Get-Date #first initialization, must be outside loop Send-ErrorsToLog $LogFile $Msg = @("Starting Parameters: " "Algorithm: " + [string]($Algorithm -join ",") "PoolsName: " + [string]($PoolsName -join ",") "CoinsName: " + [string]($CoinsName -join ",") "MiningMode: " + $MiningMode "GroupNames: " + [string]($GroupNames -join ",") "PercentToSwitch: " + $PercentToSwitch ) -join ' //' Log-Message $Msg -Severity Debug #Enable api if ($config.ApiPort -gt 0) { Log-Message "Starting API on port $($config.ApiPort)" -Severity Debug $ApiSharedFile = "$ScriptRoot\ApiShared" + [string](Get-Random -minimum 0 -maximum 99999999) + ".tmp" $command = "-WindowStyle minimized -noExit -executionpolicy bypass -file $ScriptRoot\Includes\ApiListener.ps1 -port " + [string]$config.ApiPort + " -SharedFile $ApiSharedFile " $APIprocess = Start-Process -FilePath "powershell.exe" -ArgumentList $command -Verb RunAs -PassThru -WindowStyle Minimized #open firewall port $command = "New-NetFirewallRule -DisplayName '$Application' -Direction Inbound -Action Allow -Protocol TCP -LocalPort $($config.ApiPort)" Start-Process -FilePath "powershell.exe" -ArgumentList $command -Verb RunAs -WindowStyle Minimized $command = "New-NetFirewallRule -DisplayName '$Application' -Direction Outbound -Action Allow -Protocol TCP -LocalPort $($config.ApiPort)" Start-Process -FilePath "powershell.exe" -ArgumentList $command -Verb RunAs -WindowStyle Minimized } $Quit = $false # Initialize MSI Afterburner if ((Get-ConfigVariable "Afterburner") -eq "Enabled") { . .\Includes\afterburner.ps1 } #enable EthlargementPill if (@('RevA', 'RevB') -contains $config.EthlargementPill) { Log-Message "Starting EthlargementPill" $arg = "-" + $config.EthlargementPill $EthPill = Start-Process -FilePath ".\Includes\OhGodAnETHlargementPill-r2.exe" -passthru -Verb RunAs -ArgumentList $arg } #----------------------------------------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------------------------------------- #This loop will be running forever #----------------------------------------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------------------------------------- while ($Quit -eq $false) { $Config = Get-Config Log-Message ($Config | ConvertTo-Json) -Severity Debug Clear-Host; $RepaintScreen = $true # Check for updates try { $Request = Invoke-APIRequest -Url "https://api.github.com/repos/yuzi-co/$Application/releases/latest" -Age 60 $RemoteVersion = ($Request.tag_name -replace '[^\d.]') $Uri = $Request.assets | Where-Object Name -eq "$Application-$RemoteVersion.7z" | Select-Object -ExpandProperty browser_download_url if ([version]$RemoteVersion -gt [version]$Release) { Log-Message "$Application is out of date. There is an updated version available at $URI" -Severity Warn } } catch { Log-Message "Failed to get $Application updates." -Severity Warn } #get mining types $DeviceGroups = Get-MiningTypes -filter $GroupNames if ($null -eq $Interval.Last) { Log-Message (Get-DevicesInformation $DeviceGroups | ConvertTo-Json) -Severity Debug Log-Message ($DeviceGroups | ConvertTo-Json) -Severity Debug Test-DeviceGroupsConfig $DeviceGroups } $NumberTypesGroups = ($DeviceGroups | Measure-Object).count if ($NumberTypesGroups -gt 0) {$InitialProfitsScreenLimit = [Math]::Floor(30 / $NumberTypesGroups) - 5} #screen adjust to number of groups if ($null -eq $Interval.Last) {$ProfitsScreenLimit = $InitialProfitsScreenLimit} Log-Message "New interval starting..." Log-Message (Get-ComputerStats | ConvertTo-Json) -Severity Debug $Location = $Config.Location if ([string]::IsNullOrWhiteSpace($PercentToSwitch)) {$PercentToSwitch2 = [int]($Config.PercentToSwitch)} else {$PercentToSwitch2 = [int]$PercentToSwitch} $DelayCloseMiners = $Config.DelayCloseMiners $BenchmarkIntervalTime = [int]($Config.BenchmarkTime) $LocalCurrency = $Config.LocalCurrency if ([string]::IsNullOrWhiteSpace($LocalCurrency)) { #for old config.ini compatibility $LocalCurrency = switch ($Location) { 'Europe' {"EUR"} 'EU' {"EUR"} 'US' {"USD"} 'ASIA' {"USD"} 'GB' {"GBP"} default {"USD"} } } $Interval.Last = $Interval.Current $Interval.LastTime = (Get-Date) - $Interval.StartTime $Interval.StartTime = Get-Date #Donation $DonationStat = if (Test-Path -Path 'Donation.ctr') { (Get-Content -Path 'Donation.ctr') -split '_' } else { 0, 0 } $DonationPastTime = [int]$DonationStat[0] $DonatedTime = [int]$DonationStat[1] $ElapsedDonationTime = [int]($DonationPastTime + $Interval.LastTime.TotalMinutes) $ElapsedDonatedTime = [int]($DonatedTime + $Interval.LastTime.TotalMinutes) $ConfigDonateTime = [math]::Max([int]($Config.Donate), 10) #Activate or deactivate donation if ($ElapsedDonationTime -gt 1440 -and $ConfigDonateTime -gt 0) { # donation interval $Interval.Current = "Donate" $Config.UserName = "ffwd" $Config.WorkerName = "Donate" $CoinsWallets = @{ BTC = "3NoVvkGSNjPX8xBMWbP2HioWYK395wSzGL" } $DonateInterval = ($ConfigDonateTime - $ElapsedDonatedTime) * 60 $Algorithm = $null $PoolsName = ("NiceHash") $CoinsName = $null $MiningMode = "Automatic" if ($ElapsedDonatedTime -ge $ConfigDonateTime) {"0_0" | Set-Content -Path 'Donation.ctr'} else {[string]$DonationPastTime + "_" + [string]$ElapsedDonatedTime | Set-Content -Path 'Donation.ctr'} Log-Message "Next interval you will be donating for $DonateInterval seconds, thanks for your support" } else { #NOT donation interval $Interval.Current = "Mining" $Algorithm = $ParamAlgorithmBCK $PoolsName = $ParamPoolsNameBCK $CoinsName = $ParamCoinsNameBCK $MiningMode = $ParamMiningModeBCK if (!$Config.WorkerName) {$Config.WorkerName = (Get-Culture).TextInfo.ToTitleCase(($env:COMPUTERNAME).ToLower())} $CoinsWallets = @{} switch -regex -file config.ini { "^\s*WALLET_(\w+)\s*=\s*(.*)" { $name, $value = $matches[1..2] $CoinsWallets[$name] = $value.Trim() } } [string]$ElapsedDonationTime + "_0" | Set-Content -Path Donation.ctr } $Currency = $Config.Currency $UserName = $Config.UserName $WorkerName = $Config.WorkerName $MinerWindowStyle = $Config.MinerWindowStyle if ([string]::IsNullOrEmpty($MinerWindowStyle)) {$MinerWindowStyle = 'Minimized'} $MinerStatusUrl = $Config.MinerStatusUrl $MinerStatusKey = $Config.MinerStatusKey if (!$MinerStatusKey -and $CoinsWallets.BTC) {$MinerStatusKey = $CoinsWallets.BTC} Send-ErrorsToLog $LogFile #get actual hour electricity cost ($Config.ElectricityCost | ConvertFrom-Json) | ForEach-Object { if (( $_.HourStart -lt $_.HourEnd -and @(($_.HourStart)..($_.HourEnd)) -contains (Get-Date).Hour ) -or ( $_.HourStart -gt $_.HourEnd -and ( @(($_.HourStart)..23) -contains (Get-Date).Hour -or @(0..($_.HourEnd)) -contains (Get-Date).Hour ) ) ) {$ElectricityCostValue = [double]$_.CostKwh} } Log-Message "Loading Pools Information..." #Load information about the Pools, only must read parameter passed files (not all as mph do), level is Pool-Algo-Coin do { $Params = @{ Querymode = "core" PoolsFilterList = $PoolsName CoinFilterList = $CoinsName Location = $Location AlgoFilterList = $Algorithm } $AllPools = Get-Pools @Params if ($AllPools.Count -eq 0) { Log-Message "NO POOLS! Retry in 30 seconds" -Severity Warn Log-Message "If you are mining on anonymous pool without exchage, like YIIMP, NANOPOOL or similar, you must set wallet for at least one pool coin in config.ini" -Severity Warn Start-Sleep 30 } } while ($AllPools.Count -eq 0) $AllPools | Select-Object name -unique | ForEach-Object {Log-Message "Pool $($_.Name) was responsive..."} Log-Message "Detected $($AllPools.Count) pools..." #Filter by minworkers variable (only if there is any pool greater than minimum) $Pools = ($AllPools | Where-Object {$_.PoolWorkers -ge $Config.MinWorkers -or $_.PoolWorkers -eq $null}) if ($Pools.Count -ge 1) { Log-Message "$($Pools.Count) pools left after min workers filter..." } else { $Pools = $AllPools Log-Message "No pools with workers greater than minimum config, filter ignored..." } ## Select highest paying pool for each algo and check if pool is alive. Log-Message "Select top paying pool for each algo in config" if ($config.PingPools -eq 'Enabled') {Log-Message "Checking pool availability"} $PoolsFiltered = $Pools | Group-Object -Property Algorithm | ForEach-Object { $NeedPool = $false foreach ($DeviceGroup in $DeviceGroups) { ## Is pool algorithm defined in config? $AlgoList = $DeviceGroup.Algorithms | ForEach-Object {$_ -split '_'} | Select-Object -Unique if (!$AlgoList -or $AlgoList -contains $_.Name) {$NeedPool = $true} } if ($NeedPool) { ## Order by price (profitability) $_.Group | Sort-Object -Property ` @{Expression = $(if ($MiningMode -eq 'Automatic24h') {"Price24h"} else {"Price"}); Descending = $true}, @{Expression = "LocationPriority"; Ascending = $true} | ForEach-Object { if ($NeedPool) { ## test tcp connection to pool if ($config.PingPools -ne 'Enabled' -or (Test-TCPPort -Server $_.Host -Port $_.Port -Timeout 100)) { $NeedPool = $false $_ ## return result } else { Log-Message "$($_.PoolName): $($_.Host):$($_.Port) is not responding!" -Severity Warn } } } } } $Pools = $PoolsFiltered Log-Message "$($Pools.Count) pools left" Remove-Variable PoolsFiltered #Call api to local currency conversion try { $CDKResponse = Invoke-APIRequest -Url "https://api.coindesk.com/v1/bpi/currentprice/$LocalCurrency.json" -MaxAge 60 | Select-Object -ExpandProperty BPI $LocalBTCvalue = $CDKResponse.$LocalCurrency.rate_float Log-Message "CoinDesk API was responsive..." } catch { Log-Message "Coindesk API not responding, no local coin conversion..." -Severity Warn } #Load information about the Miner asociated to each Coin-Algo-Miner $Miners = @() $MinersFolderContent = (Get-ChildItem "Miners" -Filter "*.json") Log-Message "Files in miner folder: $($MinersFolderContent.count)" -Severity Debug Log-Message "Number of device groups: $($DeviceGroups.count)" -Severity Debug foreach ($MinerFile in $MinersFolderContent) { try { $Miner = $MinerFile | Get-Content | ConvertFrom-Json } catch { Log-Message "Badly formed JSON: $MinerFile" -Severity Warn Exit } foreach ($DeviceGroup in $DeviceGroups) { #generate a line for each device group that has algorithm as valid if ($Miner.Type -ne $DeviceGroup.type) { Log-Message "$($MinerFile.pschildname) is NOT valid for $($DeviceGroup.GroupName). Skipping" -Severity Debug Continue } elseif ($Config.("ExcludeMiners_" + $DeviceGroup.GroupName) -and ($Config.("ExcludeMiners_" + $DeviceGroup.GroupName).split(',') | Where-Object {$MinerFile.BaseName -like $_})) { Log-Message "$($MinerFile.pschildname) is Excluded for $($DeviceGroup.GroupName). Skipping" -Severity Debug Continue } else { #check group and miner types are the same Log-Message "$($MinerFile.pschildname) is valid for $($DeviceGroup.GroupName)" -Severity Debug } foreach ($Algo in $Miner.Algorithms.PSObject.Properties) { ##AlgoName contains real name for dual and no dual miners $AlgoTmp = ($Algo.Name -split "\|")[0] $AlgoLabel = ($Algo.Name -split ("\|"))[1] $AlgoName = Get-AlgoUnifiedName (($AlgoTmp -split ("_"))[0]) $AlgoNameDual = Get-AlgoUnifiedName (($AlgoTmp -split ("_"))[1]) $Algorithms = $AlgoName + $(if ($AlgoNameDual) {"_$AlgoNameDual"}) # Check memory constraints on miners if ($DeviceGroup.MemoryGB -gt 0) { $SkipLabel = $false if ($AlgoLabel -match '(?<mem>\d+)gb.*') { if ($DeviceGroup.MemoryGB -lt [int]$Matches.mem) {$SkipLabel = $true} } if ($SkipLabel) { Log-Message "$($MinerFile.BaseName)/$Algorithms/$AlgoLabel skipped due to constraints" -Severity Debug Continue } } if ($Config.CUDAVersion -and $Miner.CUDA) { if ([version]$Miner.CUDA -gt [version]$Config.CUDAVersion) { Log-Message "$($MinerFile.BaseName) skipped due to CUDA version constraints" -Severity Debug Continue } } if ($DeviceGroup.Algorithms -and $DeviceGroup.Algorithms -notcontains $Algorithms) {Continue} #check config has this algo as minable foreach ($Pool in ($Pools | Where-Object Algorithm -eq $AlgoName)) { #Search pools for that algo # If Miner limited to pools if ($Miner.Pools -and $ExecutionContext.InvokeCommand.ExpandString($Miner.Pools) -eq $false) {Continue} if (!$AlgoNameDual -or ($Pools | Where-Object Algorithm -eq $AlgoNameDual)) { #Set flag if both Miner and Pool support SSL $enableSSL = [bool]($Miner.SSL -and $Pool.SSL) #Replace wildcards patterns if ($Pool.PoolName -eq 'Nicehash') {$Nicehash = $true} else {$Nicehash = $false} if ($Nicehash) { $WorkerNameMain = $WorkerName + '-' + $DeviceGroup.GroupName #Nicehash requires alphanumeric WorkerNames } else { $WorkerNameMain = $WorkerName + '_' + $DeviceGroup.GroupName } $PoolUser = $Pool.User -replace '#WorkerName#', $WorkerNameMain $PoolPass = $Pool.Pass -replace '#WorkerName#', $WorkerNameMain $Params = @{ '#Protocol#' = $(if ($enableSSL) {$Pool.ProtocolSSL} else {$Pool.Protocol}) '#Server#' = $(if ($enableSSL) {$Pool.HostSSL} else {$Pool.Host}) '#Port#' = $(if ($enableSSL) {$Pool.PortSSL} else {$Pool.Port}) '#Login#' = $PoolUser '#Password#' = $PoolPass '#GPUPlatform#' = $DeviceGroup.Platform '#Algorithm#' = $AlgoName '#AlgorithmParameters#' = $Algo.Value '#WorkerName#' = $WorkerNameMain '#Devices#' = $DeviceGroup.Devices '#DevicesClayMode#' = $DeviceGroup.DevicesClayMode '#DevicesETHMode#' = $DeviceGroup.DevicesETHMode '#DevicesNsgMode#' = $DeviceGroup.DevicesNsgMode '#EthStMode#' = $Pool.EthStMode '#GroupName#' = $DeviceGroup.GroupName '#EMail#' = $Config.EMail } $Arguments = $Miner.Arguments -join " " $Arguments = $Arguments -replace '#AlgorithmParameters#', $Algo.Value foreach ($P in $Params.Keys) {$Arguments = $Arguments -replace $P, $Params.$P} $PatternConfigFile = $Miner.PatternConfigFile -replace '#Algorithm#', $AlgoName -replace '#GroupName#', $DeviceGroup.GroupName if ($PatternConfigFile -and (Test-Path -Path $PatternConfigFile)) { $ConfigFileArguments = Replace-ForEachDevice (Get-Content $PatternConfigFile -raw) -Devices $DeviceGroup foreach ($P in $Params.Keys) {$ConfigFileArguments = $ConfigFileArguments -replace $P, $Params.$P} } #select correct price by mode $Price = $Pool.$(if ($MiningMode -eq 'Automatic24h') {"Price24h"} else {"Price"}) #Search for dualmining pool if ($AlgoNameDual) { #search dual pool and select correct price by mode $PoolDual = $Pools | Where-Object Algorithm -eq $AlgoNameDual | Sort-Object @{Expression = $(if ($MiningMode -eq 'Automatic24h') {"Price24h"} else {"Price"}); Descending = $true} | Select-Object -First 1 $PriceDual = [double]$PoolDual.$(if ($MiningMode -eq 'Automatic24h') {"Price24h"} else {"Price"}) #Set flag if both Miner and Pool support SSL $enableDualSSL = ($Miner.SSL -and $PoolDual.SSL) #Replace wildcards patterns if ($PoolDual.PoolName -eq 'Nicehash') { $WorkerNameDual = $WorkerName + '-' + $DeviceGroup.GroupName + 'D' #Nicehash requires alphanumeric WorkerNames } else { $WorkerNameDual = $WorkerName + '_' + $DeviceGroup.GroupName + 'D' } $PoolUserDual = $PoolDual.User -replace '#WorkerName#', $WorkerNameDual $PoolPassDual = $PoolDual.Pass -replace '#WorkerName#', $WorkerNameDual $Params = @{ '#PortDual#' = $(if ($enableDualSSL) {$PoolDual.PortSSL} else {$PoolDual.Port}) '#ServerDual#' = $(if ($enableDualSSL) {$PoolDual.HostSSL} else {$PoolDual.Host}) '#ProtocolDual#' = $(if ($enableDualSSL) {$PoolDual.ProtocolSSL} else {$PoolDual.Protocol}) '#LoginDual#' = $PoolUserDual '#PasswordDual#' = $PoolPassDual '#AlgorithmDual#' = $AlgoNameDual '#WorkerName#' = $WorkerNameDual } foreach ($P in $Params.Keys) {$Arguments = $Arguments -replace $P, $Params.$P} if ($PatternConfigFile -and (Test-Path -Path $PatternConfigFile)) { foreach ($P in $Params.Keys) {$ConfigFileArguments = $ConfigFileArguments -replace $P, $Params.$P} } } else { $PoolDual = $null $PoolUserDual = $null $PriceDual = 0 } ## SubMiner are variations of miner that not need to relaunch ## Creates a "SubMiner" object for each PL $SubMiners = @() foreach ($PowerLimit in ($DeviceGroup.PowerLimits)) { ## always exists as least a power limit 0 ## look in ActiveMiners collection if we found that miner to conserve some properties and not read files $FoundMiner = $ActiveMiners | Where-Object { $_.Name -eq $MinerFile.BaseName -and $_.Coin -eq $Pool.Info -and $_.Algorithm -eq $AlgoName -and $_.CoinDual -eq $PoolDual.Info -and $_.AlgorithmDual -eq $AlgoNameDual -and $_.PoolAbbName -eq $Pool.AbbName -and $_.PoolAbbNameDual -eq $PoolDual.AbbName -and $_.DeviceGroup.Id -eq $DeviceGroup.Id -and $_.AlgoLabel -eq $AlgoLabel } $FoundSubMiner = $FoundMiner.SubMiners | Where-Object {$_.PowerLimit -eq $PowerLimit} if (!$FoundSubMiner) { $Params = @{ Algorithm = $Algorithms MinerName = $MinerFile.BaseName GroupName = $DeviceGroup.GroupName PowerLimit = $PowerLimit AlgoLabel = $AlgoLabel } [array]$Hrs = Get-HashRates @Params } else { [array]$Hrs = $FoundSubMiner.SpeedReads } if ($Hrs.Count -gt 10) { # Remove 10 percent of lowest and highest rate samples which may skew the average $Hrs = $Hrs | Sort-Object Speed $p5Index = [math]::Ceiling($Hrs.Count * 0.05) $p95Index = [math]::Ceiling($Hrs.Count * 0.95) $Hrs = $Hrs[$p5Index..$p95Index] | Sort-Object SpeedDual, Speed $p5Index = [math]::Ceiling($Hrs.Count * 0.05) $p95Index = [math]::Ceiling($Hrs.Count * 0.95) $Hrs = $Hrs[$p5Index..$p95Index] $PowerValue = [double]($Hrs | Measure-Object -property Power -average).average $HashRateValue = [double]($Hrs | Measure-Object -property Speed -average).average $HashRateValueDual = [double]($Hrs | Measure-Object -property SpeedDual -average).average } else { $PowerValue = 0 $HashRateValue = 0 $HashRateValueDual = 0 } #calculates revenue $SubMinerRevenue = [double]($HashRateValue * $Price) $SubMinerRevenueDual = [double]($HashRateValueDual * $PriceDual) #apply fee to revenues $MinerFee = [decimal]$ExecutionContext.InvokeCommand.ExpandString($Miner.Fee) $SubMinerRevenue *= (1 - $MinerFee) if (!$FoundSubMiner) { $Params = @{ Algorithm = $Algorithms MinerName = $MinerFile.BaseName GroupName = $DeviceGroup.GroupName PowerLimit = $PowerLimit AlgoLabel = $AlgoLabel } $StatsHistory = Get-Stats @Params } else { $StatsHistory = $FoundSubMiner.StatsHistory } $Stats = [PSCustomObject]@{ BestTimes = 0 BenchmarkedTimes = 0 LastTimeActive = [DateTime]0 ActivatedTimes = 0 ActiveTime = 0 FailedTimes = 0 StatsTime = [DateTime]0 } if (!$StatsHistory) {$StatsHistory = $Stats} if ($SubMiners.Count -eq 0 -or $SubMiners[0].StatsHistory.BestTimes -gt 0) { #only add a SubMiner (distinct from first if sometime first was best) $SubMiners += [PSCustomObject]@{ Id = $SubMiners.Count Best = $false BestBySwitch = "" HashRate = $HashRateValue HashRateDual = $HashRateValueDual NeedBenchmark = [bool]($HashRateValue -eq 0 -or ($AlgorithmDual -and $HashRateValueDual -eq 0)) PowerAvg = $PowerValue PowerLimit = [int]$PowerLimit PowerLive = 0 Profits = (($SubMinerRevenue + $SubMinerRevenueDual) * $localBTCvalue) - ($ElectricityCostValue * ($PowerValue * 24) / 1000) #Profit is revenue less electricity cost ProfitsLive = 0 Revenue = $SubMinerRevenue RevenueDual = $SubMinerRevenueDual RevenueLive = 0 RevenueLiveDual = 0 SpeedLive = 0 SpeedLiveDual = 0 SpeedReads = if ($null -ne $Hrs) {[array]$Hrs} else {@()} Status = 'Idle' Stats = $Stats StatsHistory = $StatsHistory TimeSinceStartInterval = [TimeSpan]0 } } } #end foreach PowerLimit $Miners += [PSCustomObject] @{ AlgoLabel = $AlgoLabel Algorithm = $AlgoName AlgorithmDual = $AlgoNameDual Algorithms = $Algorithms API = $ExecutionContext.InvokeCommand.ExpandString($Miner.API) Arguments = $ExecutionContext.InvokeCommand.ExpandString($Arguments) BenchmarkArg = $ExecutionContext.InvokeCommand.ExpandString($Miner.BenchmarkArg) Coin = $Pool.Info CoinDual = $PoolDual.Info ConfigFileArguments = $ExecutionContext.InvokeCommand.ExpandString($ConfigFileArguments) ExtractionPath = $(".\Bin\" + $MinerFile.BaseName + "\") GenerateConfigFile = $(if ($PatternConfigFile) {".\Bin\" + $MinerFile.BaseName + "\" + $Miner.GenerateConfigFile -replace '#GroupName#', $DeviceGroup.GroupName -replace '#Algorithm#', $AlgoName}) DeviceGroup = $DeviceGroup Host = $Pool.Host Location = $Pool.Location MinerFee = $MinerFee Name = $MinerFile.BaseName Path = $(".\Bin\" + $MinerFile.BaseName + "\" + $ExecutionContext.InvokeCommand.ExpandString($Miner.Path)) PoolAbbName = $Pool.AbbName PoolAbbNameDual = $PoolDual.AbbName PoolFee = [double]$Pool.Fee PoolFeeDual = [double]$PoolDual.Fee PoolName = $Pool.PoolName PoolNameDual = $PoolDual.PoolName PoolPrice = $(if ($MiningMode -eq 'Automatic24h') {[double]$Pool.Price24h} else {[double]$Pool.Price}) PoolPriceDual = $(if ($MiningMode -eq 'Automatic24h') {[double]$PoolDual.Price24h} else {[double]$PoolDual.Price}) PoolRewardType = $Pool.RewardType PoolWorkers = $Pool.PoolWorkers PoolWorkersDual = $PoolDual.PoolWorkers Port = $(if (($DeviceGroups | Where-Object type -eq $DeviceGroup.type).Count -le 1 -and $DelayCloseMiners -eq 0 -and $config.ForceDynamicPorts -ne "Enabled") { $Miner.ApiPort }) PrelaunchCommand = $ExecutionContext.InvokeCommand.ExpandString($Miner.PrelaunchCommand) PrerequisitePath = $Miner.PrerequisitePath PrerequisiteURI = $Miner.PrerequisiteURI SubMiners = $SubMiners SHA256 = $Miner.SHA256 Symbol = $Pool.Symbol SymbolDual = $PoolDual.Symbol URI = $Miner.URI UserName = $PoolUser UserNameDual = $PoolUserDual WalletMode = $Pool.WalletMode WalletModeDual = $PoolDual.WalletMode WalletSymbol = $Pool.WalletSymbol WalletSymbolDual = $PoolDual.WalletSymbol WorkerName = $WorkerNameMain WorkerNameDual = $WorkerNameDual } } #dualmining } #end foreach pool } #end foreach algo } # end if types } #end foreach miner Log-Message "Miners/Pools combinations detected: $($Miners.Count)" #Launch download of miners $Miners | Where-Object { -not [string]::IsNullOrEmpty($_.URI) -and -not [string]::IsNullOrEmpty($_.ExtractionPath) -and -not [string]::IsNullOrEmpty($_.Path)} | Select-Object URI, ExtractionPath, Path, SHA256 -Unique | ForEach-Object { if (-not (Test-Path $_.Path)) {Start-Downloader -URI $_.URI -ExtractionPath $_.ExtractionPath -Path $_.Path -SHA256 $_.SHA256} } #Launch download of prerequisites $Miners | Where-Object PrerequisitePath | Select-Object PrerequisitePath, PrerequisiteURI -Unique | ForEach-Object { $_.PrerequisitePath = $ExecutionContext.InvokeCommand.ExpandString($_.PrerequisitePath) if (-not (Test-Path $_.PrerequisitePath)) { Start-Downloader -URI $_.PrerequisiteURI -Path $_.PrerequisitePath -ExtractionPath $_.PrerequisitePath } } Send-ErrorsToLog $LogFile #Paint no miners message $Miners = $Miners | Where-Object {Test-Path $_.Path} if ($Miners.Count -eq 0) { Log-Message "NO MINERS! Retry in 30 seconds..." -Severity Warn Start-Sleep -Seconds 30 Continue } #Update the active miners list which is alive for all execution time foreach ($ActiveMiner in ($ActiveMiners | Sort-Object [int]id)) { #Search existing miners to update data $Miner = $Miners | Where-Object { $_.Name -eq $ActiveMiner.Name -and $_.Coin -eq $ActiveMiner.Coin -and $_.Algorithm -eq $ActiveMiner.Algorithm -and $_.CoinDual -eq $ActiveMiner.CoinDual -and $_.AlgorithmDual -eq $ActiveMiner.AlgorithmDual -and $_.PoolAbbName -eq $ActiveMiner.PoolAbbName -and $_.PoolAbbNameDual -eq $ActiveMiner.PoolAbbNameDual -and $_.DeviceGroup.Id -eq $ActiveMiner.DeviceGroup.Id -and $_.AlgoLabel -eq $ActiveMiner.AlgoLabel } if (($Miner | Measure-Object).count -gt 1) { Clear-Host Log-Message "DUPLICATE MINER $($Miner.Algorithms) in $($Miner.Name)" -Severity Warn Exit } if ($Miner) { # we found that miner $ActiveMiner.Arguments = $Miner.Arguments $ActiveMiner.PoolPrice = $Miner.PoolPrice $ActiveMiner.PoolPriceDual = $Miner.PoolPriceDual $ActiveMiner.PoolFee = $Miner.PoolFee $ActiveMiner.PoolFeeDual = $Miner.PoolFeeDual $ActiveMiner.PoolWorkers = $Miner.PoolWorkers $ActiveMiner.IsValid = $true foreach ($SubMiner in $Miner.SubMiners) { if (($ActiveMiner.SubMiners | Where-Object {$_.Id -eq $SubMiner.Id}).Count -eq 0) { $SubMiner | Add-Member IdF $ActiveMiner.Id $ActiveMiner.SubMiners += $SubMiner } else { $ActiveMiner.SubMiners[$SubMiner.Id].HashRate = $SubMiner.HashRate $ActiveMiner.SubMiners[$SubMiner.Id].HashRateDual = $SubMiner.HashRateDual $ActiveMiner.SubMiners[$SubMiner.Id].NeedBenchmark = $SubMiner.NeedBenchmark $ActiveMiner.SubMiners[$SubMiner.Id].PowerAvg = $SubMiner.PowerAvg $ActiveMiner.SubMiners[$SubMiner.Id].Profits = $SubMiner.Profits $ActiveMiner.SubMiners[$SubMiner.Id].Revenue = $SubMiner.Revenue $ActiveMiner.SubMiners[$SubMiner.Id].RevenueDual = $SubMiner.RevenueDual } } } else { #An existing miner is not found now $ActiveMiner.IsValid = $false } } ##Add new miners to list foreach ($Miner in $Miners) { $ActiveMiner = $ActiveMiners | Where-Object { $_.Name -eq $Miner.Name -and $_.Coin -eq $Miner.Coin -and $_.Algorithm -eq $Miner.Algorithm -and $_.CoinDual -eq $Miner.CoinDual -and $_.AlgorithmDual -eq $Miner.AlgorithmDual -and $_.PoolAbbName -eq $Miner.PoolAbbName -and $_.PoolAbbNameDual -eq $Miner.PoolAbbNameDual -and $_.DeviceGroup.Id -eq $Miner.DeviceGroup.Id -and $_.AlgoLabel -eq $Miner.AlgoLabel} if (!$ActiveMiner) { $Miner.SubMiners | Add-Member IdF $ActiveMiners.Count $ActiveMiners += [PSCustomObject]@{ AlgoLabel = $Miner.AlgoLabel Algorithm = $Miner.Algorithm AlgorithmDual = $Miner.AlgorithmDual Algorithms = $Miner.Algorithms API = $Miner.API Arguments = $Miner.Arguments BenchmarkArg = $Miner.BenchmarkArg Coin = $Miner.Coin CoinDual = $Miner.CoinDual ConfigFileArguments = $Miner.ConfigFileArguments GenerateConfigFile = $Miner.GenerateConfigFile DeviceGroup = $Miner.DeviceGroup Host = $Miner.Host Id = $ActiveMiners.Count IsValid = $true Location = $Miner.Location MinerFee = $Miner.MinerFee Name = $Miner.Name Path = Convert-Path $Miner.Path PoolAbbName = $Miner.PoolAbbName PoolAbbNameDual = $Miner.PoolAbbNameDual PoolFee = $Miner.PoolFee PoolFeeDual = $Miner.PoolFeeDual PoolName = $Miner.PoolName PoolNameDual = $Miner.PoolNameDual PoolPrice = $Miner.PoolPrice PoolPriceDual = $Miner.PoolPriceDual PoolWorkers = $Miner.PoolWorkers PoolHashRate = $null PoolHashRateDual = $null PoolRewardType = $Miner.PoolRewardType Port = $Miner.Port PrelaunchCommand = $Miner.PrelaunchCommand Process = $null SubMiners = $Miner.SubMiners Symbol = $Miner.Symbol SymbolDual = $Miner.SymbolDual UserName = $Miner.UserName UserNameDual = $Miner.UserNameDual WalletMode = $Miner.WalletMode WalletSymbol = $Miner.WalletSymbol WorkerName = $Miner.WorkerName WorkerNameDual = $Miner.WorkerNameDual } } } ## Reset failed miners after 4 hours $ActiveMiners.SubMiners | Where-Object {$_.Status -eq 'Failed' -and $_.Stats.LastTimeActive -lt (Get-Date).AddHours(-4)} | ForEach-Object { $_.Status = 'Idle' $_.Stats.FailedTimes = 0 Log-Message "Reset failed miner status: $($ActiveMiners[$_.IdF].Name)/$($ActiveMiners[$_.IdF].Algorithms)" } Log-Message "Active Miners-pools: $($ActiveMiners.Count)" Send-ErrorsToLog $LogFile Log-Message "Pending benchmarks: $(($ActiveMiners | Where-Object IsValid | Select-Object -ExpandProperty SubMiners | Where-Object NeedBenchmark | Select-Object -ExpandProperty Id).Count)..." $Msg = ($ActiveMiners.SubMiners | ForEach-Object { "$($_.IdF)-$($_.Id), " + "$($ActiveMiners[$_.IdF].DeviceGroup.GroupName), " + "$(if ($ActiveMiners[$_.IdF].IsValid) {'Valid'} else {'Invalid'}), " + "PL $($_.PowerLimit), " + "$($_.Status), " + "$($ActiveMiners[$_.IdF].Name), " + "$($ActiveMiners[$_.IdF].Algorithms), " + "$($ActiveMiners[$_.IdF].Coin), " + "$($ActiveMiners[$_.IdF].Process.Id)" }) | ConvertTo-Json Log-Message $Msg -Severity Debug #For each type, select most profitable miner, not benchmarked has priority, new miner is only lauched if new profit is greater than old by percenttoswitch #This section changes SubMiner foreach ($DeviceGroup in $DeviceGroups) { #look for last round best $Candidates = $ActiveMiners | Where-Object {$_.DeviceGroup.Id -eq $DeviceGroup.Id} $BestLast = $Candidates.SubMiners | Where-Object {@("Running", "PendingCancellation") -contains $_.Status} if ($BestLast) { $ProfitLast = $BestLast.Profits $BestLastLogMsg = $( "$($ActiveMiners[$BestLast.IdF].Name)/" + "$($ActiveMiners[$BestLast.IdF].Algorithms)/" + "$($ActiveMiners[$BestLast.IdF].Coin)" + "$(if ($ActiveMiners[$BestLast.IdF].CoinDual) { '_' + $ActiveMiners[$BestLast.IdF].CoinDual}) " + "with Power Limit $($BestLast.PowerLimit) " + "(id $($BestLast.IdF)-$($BestLast.Id)) " + "for group $($DeviceGroup.GroupName)") # cancel miner if current pool workers below MinWorkers if ( $ActiveMiners[$BestLast.IdF].PoolWorkers -ne $null -and $ActiveMiners[$BestLast.IdF].PoolWorkers -le $config.MinWorkers ) { $BestLast.Status = 'PendingCancellation' Log-Message "Cancelling miner due to low worker count" } } else { $ProfitLast = 0 } if ($BestLast -and $config.SessionStatistics -eq 'Enabled') { $BestLast | Select-Object -Property ` @{Name = "Date"; Expression = {Get-Date -f "yyyy-MM-dd"}}, @{Name = "Time"; Expression = {Get-Date -f "HH:mm:ss"}}, @{Name = "Group"; Expression = {$DeviceGroup.GroupName}}, @{Name = "Name"; Expression = {$ActiveMiners[$_.IdF].Name}}, @{Name = "Algorithm"; Expression = {$ActiveMiners[$_.IdF].Algorithm}}, @{Name = "AlgorithmDual"; Expression = {$ActiveMiners[$_.IdF].AlgorithmDual}}, @{Name = "AlgoLabel"; Expression = {$ActiveMiners[$_.IdF].AlgoLabel}}, @{Name = "Coin"; Expression = {$ActiveMiners[$_.IdF].Coin}}, @{Name = "CoinDual"; Expression = {$ActiveMiners[$_.IdF].CoinDual}}, @{Name = "PoolName"; Expression = {$ActiveMiners[$_.IdF].PoolName}}, @{Name = "PoolNameDual"; Expression = {$ActiveMiners[$_.IdF].PoolNameDual}}, @{Name = "PowerLimit"; Expression = {$_.PowerLimit}}, @{Name = "HashRate"; Expression = {[decimal]$_.HashRate}}, @{Name = "HashRateDual"; Expression = {[decimal]$_.HashRateDual}}, @{Name = "Revenue"; Expression = {[decimal]$_.Revenue}}, @{Name = "RevenueDual"; Expression = {[decimal]$_.RevenueDual}}, @{Name = "Profits"; Expression = {[decimal]$_.Profits}}, @{Name = "IntervalRevenue"; Expression = {[decimal]$_.Revenue * $Interval.LastTime.TotalSeconds / (24 * 60 * 60)}}, @{Name = "IntervalRevenueDual"; Expression = {[decimal]$_.RevenueDual * $Interval.LastTime.TotalSeconds / (24 * 60 * 60)}}, @{Name = "Interval"; Expression = {[int]$Interval.LastTime.TotalSeconds}} | Export-Csv -Path $(".\Logs\Stats-" + (Get-Process -PID $PID).StartTime.tostring('yyyy-MM-dd_HH-mm-ss') + ".csv") -Append -NoTypeInformation } #check if must cancel miner/algo/coin combo if ($BestLast.Status -eq 'PendingCancellation') { if (($ActiveMiners[$BestLast.IdF].SubMiners.Stats.FailedTimes | Measure-Object -sum).sum -ge 3) { $ActiveMiners[$BestLast.IdF].SubMiners | ForEach-Object {$_.Status = 'Failed'} Log-Message "Detected more than 3 fails, cancelling combination for $BestNowLogMsg" -Severity Warn } } # look for best for next round $Candidates = $ActiveMiners | Where-Object {$_.DeviceGroup.Id -eq $DeviceGroup.Id -and $_.IsValid -and $_.UserName} ## Select top miner that need Benchmark, or if running in Manual mode, or highest Profit above zero. $BestNow = $Candidates.SubMiners | Where-Object Status -ne 'Failed' | Where-Object { $_.NeedBenchmark -or $_.Profits -gt $Config.('MinProfit_' + $DeviceGroup.GroupName) -or -not $LocalBTCvalue -gt 0 -or $MiningMode -eq "Manual" -or $Interval.Current -eq "Donate" } | Sort-Object -Descending NeedBenchmark, {$(if ($MiningMode -eq "Manual") {$_.HashRate} else {$_.Profits})}, {$ActiveMiners[$_.IdF].PoolPrice}, {$ActiveMiners[$_.IdF].PoolPriceDual}, PowerLimit | Select-Object -First 1 if ($BestNow) { $BestNowLogMsg = $( "$($ActiveMiners[$BestNow.IdF].Name)/" + "$($ActiveMiners[$BestNow.IdF].Algorithms)/" + "$($ActiveMiners[$BestNow.IdF].Coin)" + "$(if ($ActiveMiners[$BestNow.IdF].CoinDual) { '_' + $ActiveMiners[$BestNow.IdF].CoinDual}) " + "with Power Limit $($BestNow.PowerLimit) " + "(id $($BestNow.IdF)-$($BestNow.Id))" "for group $($DeviceGroup.GroupName)") $ProfitNow = $BestNow.Profits if ($BestNow.NeedBenchmark -eq $false) { $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].BestBySwitch = "" $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].Stats.BestTimes++ $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].StatsHistory.BestTimes++ } Log-Message "$BestNowLogMsg is the best combination for device group, last was $BestLastLogMsg" } else { Log-Message "No valid candidate for device group $($DeviceGroup.GroupName)" -Severity Warn # Continue } if ( $BestLast.IdF -ne $BestNow.IdF -or $BestLast.Id -ne $BestNow.Id -or @('PendingCancellation', 'Failed') -contains $BestLast.Status -or $Interval.Current -ne $Interval.Last -or -not $BestNow ) { ### something changes or some miner error if ( $Interval.Current -ne $Interval.Last -or -not $BestLast -or -not $BestNow -or -not $ActiveMiners[$BestLast.IdF].IsValid -or $BestNow.NeedBenchmark -or @('PendingCancellation', 'Failed') -contains $BestLast.Status -or (@('Running') -contains $BestLast.Status -and $ProfitNow -gt ($ProfitLast * (1 + ($PercentToSwitch2 / 100)))) ) { #Must launch other miner and/or stop actual #Stop old if ($BestLast) { if ( $ActiveMiners[$BestLast.IdF].Process -and $ActiveMiners[$BestLast.IdF].Process.Id -gt 0 ) { Log-Message "Killing in $DelayCloseMiners sec. $BestLastLogMsg with system process id $($ActiveMiners[$BestLast.IdF].Process.Id)" if ( $DelayCloseMiners -eq 0 -or $BestNow.NeedBenchmark -or @('PendingCancellation', 'Failed') -contains $BestLast.Status ) { #immediate kill Exit-Process $ActiveMiners[$BestLast.IdF].Process } else { #delayed kill $Code = { param($Process, $DelaySeconds) Start-Sleep -Seconds $DelaySeconds $Process.CloseMainWindow() | Out-Null Stop-Process $Process.Id -force -wa SilentlyContinue -ea SilentlyContinue } Start-Job -ScriptBlock $Code -ArgumentList ($ActiveMiners[$BestLast.IdF].Process), $DelayCloseMiners } } $ActiveMiners[$BestLast.IdF].Process = $null $ActiveMiners[$BestLast.IdF].SubMiners[$BestLast.Id].Best = $false switch ($BestLast.Status) { 'Running' {$ActiveMiners[$BestLast.IdF].SubMiners[$BestLast.Id].Status = 'Idle'} 'PendingCancellation' {$ActiveMiners[$BestLast.IdF].SubMiners[$BestLast.Id].Status = 'Cancelled'} 'Failed' {$ActiveMiners[$BestLast.IdF].SubMiners[$BestLast.Id].Status = 'Failed'} } } #Start New if ($BestNow) { if ( $BestNow.PowerLimit -gt 0 -and $BestNow.PowerLimit -ne $BestLast.PowerLimit ) { if ($abControl) { Set-AfterburnerPowerLimit -PowerLimitPercent $BestNow.PowerLimit -DeviceGroup $ActiveMiners[$BestNow.IdF].DeviceGroup } elseif ($BestNow.PowerLimit -gt 0) { switch ($ActiveMiners[$BestNow.IdF].DeviceGroup.Type) { 'NVIDIA' { Set-NvidiaPowerLimit $BestNow.PowerLimit $ActiveMiners[$BestNow.IdF].DeviceGroup.Devices } Default {} } } } $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].Best = $true if ($null -eq $ActiveMiners[$BestNow.IdF].Port) { $ActiveMiners[$BestNow.IdF].Port = Get-NextFreePort (Get-Random -minimum 2000 -maximum 48000)} $ActiveMiners[$BestNow.IdF].Arguments = $ActiveMiners[$BestNow.IdF].Arguments -replace '#APIPort#', $ActiveMiners[$BestNow.IdF].Port if ($ActiveMiners[$BestNow.IdF].GenerateConfigFile) { $ActiveMiners[$BestNow.IdF].ConfigFileArguments = $ActiveMiners[$BestNow.IdF].ConfigFileArguments -replace '#APIPort#', $ActiveMiners[$BestNow.IdF].Port $ActiveMiners[$BestNow.IdF].ConfigFileArguments | Set-Content ($ActiveMiners[$BestNow.IdF].GenerateConfigFile) } if ($ActiveMiners[$BestNow.IdF].PrelaunchCommand) {Invoke-Expression $ActiveMiners[$BestNow.IdF].PrelaunchCommand} #run prelaunch command $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].Stats.ActivatedTimes++ $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].StatsHistory.ActivatedTimes++ $Arguments = $ActiveMiners[$BestNow.IdF].Arguments if ($ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].NeedBenchmark -and $ActiveMiners[$BestNow.IdF].BenchmarkArg) {$Arguments += " " + $ActiveMiners[$BestNow.IdF].BenchmarkArg } if ($ActiveMiners[$BestNow.IdF].Api -eq "Wrapper") { $ProcessParams = @{ FilePath = (Get-Process -Id $Global:PID).Path ArgumentList = "-executionpolicy bypass -command . '$(Convert-Path ".\Wrapper.ps1")' -ControllerProcessID $PID -Id '$($ActiveMiners[$BestNow.IdF].Port)' -FilePath '$($ActiveMiners[$BestNow.IdF].Path)' -ArgumentList '$($Arguments)' -WorkingDirectory '$(Split-Path $ActiveMiners[$BestNow.IdF].Path)'" } } else { $ProcessParams = @{ FilePath = $ActiveMiners[$BestNow.IdF].Path ArgumentList = $Arguments } } $CommonParams = @{ WorkingDirectory = Split-Path $ActiveMiners[$BestNow.IdF].Path MinerWindowStyle = $MinerWindowStyle Priority = if ($ActiveMiners[$BestNow.IdF].DeviceGroup.Type -eq "CPU") { -2 } else { 0 } } $ActiveMiners[$BestNow.IdF].Process = Start-SubProcess @ProcessParams @CommonParams $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].Status = 'Running' $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].BestBySwitch = "" $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].Stats.LastTimeActive = Get-Date $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].Stats.StatsTime = Get-Date $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].StatsHistory.LastTimeActive = Get-Date $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].TimeSinceStartInterval = [TimeSpan]0 Log-Message "Started System process Id $($ActiveMiners[$BestNow.IdF].Process.Id) for $BestNowLogMsg --> $($ActiveMiners[$BestNow.IdF].Path) $($ActiveMiners[$BestNow.IdF].Arguments)" -Severity Debug } } else { #Must mantain last miner by switch $ActiveMiners[$BestLast.IdF].SubMiners[$BestLast.Id].Best = $true if ($ProfitLast -lt $ProfitNow) { $ActiveMiners[$BestLast.IdF].SubMiners[$BestLast.Id].BestBySwitch = "*" Log-Message "$BestNowLogMsg Continue mining due to PercentToSwitch value" } } } if ($BestNow) { $Params = @{ Algorithm = $ActiveMiners[$BestNow.IdF].Algorithms MinerName = $ActiveMiners[$BestNow.IdF].Name GroupName = $ActiveMiners[$BestNow.IdF].DeviceGroup.GroupName AlgoLabel = $ActiveMiners[$BestNow.IdF].AlgoLabel PowerLimit = $BestNow.PowerLimit Value = $ActiveMiners[$BestNow.IdF].SubMiners[$BestNow.Id].StatsHistory } Set-Stats @Params } } if ($ActiveMiners | Where-Object IsValid | Select-Object -ExpandProperty Subminers | Where-Object {$_.NeedBenchmark -and $_.Status -ne 'Failed'}) {$Interval.Benchmark = $true} else {$Interval.Benchmark = $false} if ($Interval.Current -eq "Donate") { $Interval.Duration = $DonateInterval } elseif ($Interval.Benchmark) { $Interval.Duration = $BenchmarkIntervalTime } else { $Interval.Duration = $ActiveMiners.SubMiners | Where-Object Status -eq 'Running' | Select-Object -ExpandProperty IdF | ForEach-Object { $PoolInterval = $Config.("INTERVAL_" + $ActiveMiners[$_].PoolRewardType) Log-Message "Interval for pool $($ActiveMiners[$_].PoolName) is $PoolInterval" -Severity Debug $PoolInterval # Return value } | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum } if (-not $Interval.Duration) {$Interval.Duration = 60} # when no best miners available retry every minute Log-Message "Next interval: $($Interval.Duration)" $FirstLoopExecution = $true $LoopStartTime = Get-Date Send-ErrorsToLog $LogFile $SwitchLoop = 0 $ActivityAverages = @() Clear-Host; $RepaintScreen = $true while ($Host.UI.RawUI.KeyAvailable) {$Host.UI.RawUI.FlushInputBuffer()} #keyb buffer flush #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- #loop to update info and check if miner is running, Exit loop is forced inside while ($true) { $ExitLoop = $false if ($Config.HardwareMonitoring -ne 'Disabled') { $Devices = Get-DevicesInformation $DeviceGroups } else { $Devices = $null } ############################################################# #Check Live Speed and record benchmark if necessary $ActiveMiners.SubMiners | Where-Object Best | ForEach-Object { if ($FirstLoopExecution -and $_.NeedBenchmark) {$_.Stats.BenchmarkedTimes++; $_.StatsHistory.BenchmarkedTimes++} $_.SpeedLive = 0 $_.SpeedLiveDual = 0 $_.ProfitsLive = 0 $_.RevenueLive = 0 $_.RevenueLiveDual = 0 $Miner_HashRates = $null $Miner_HashRates = Get-LiveHashRate $ActiveMiners[$_.IdF].API $ActiveMiners[$_.IdF].Port if ($Miner_HashRates) { $_.SpeedLive = [double]($Miner_HashRates[0]) $_.SpeedLiveDual = [double]($Miner_HashRates[1]) $_.RevenueLive = $_.SpeedLive * $ActiveMiners[$_.IdF].PoolPrice $_.RevenueLiveDual = $_.SpeedLiveDual * $ActiveMiners[$_.IdF].PoolPriceDual $_.ProfitsLive = ($_.RevenueLive * (1 - $ActiveMiners[$_.IdF].MinerFee) + $_.RevenueLiveDual) * $LocalBTCvalue $_.PowerLive = ($Devices | Where-Object group -eq ($ActiveMiners[$_.IdF].DeviceGroup.GroupName) | Measure-Object -property PowerDraw -sum).sum if ($_.PowerLive) { $_.ProfitsLive -= ($ElectricityCostValue * ($_.PowerLive * 24) / 1000) } $_.TimeSinceStartInterval = (Get-Date) - $_.Stats.LastTimeActive $TimeSinceStartInterval = [int]$_.TimeSinceStartInterval.TotalSeconds if ( $_.SpeedLive -and ($_.SpeedLiveDual -or !$ActiveMiners[$_.IdF].AlgorithmDual) ) { if ($_.Stats.StatsTime) { $_.Stats.ActiveTime += ((Get-Date) - $_.Stats.StatsTime).TotalSeconds } $_.Stats.StatsTime = Get-Date [array]$_.SpeedReads = $_.SpeedReads if ($_.SpeedReads.Count -le 10 -or $_.SpeedLive -le ((($_.SpeedReads | Measure-Object -Property Speed -Average).average) * 100)) { #for avoid miners peaks recording $_.SpeedReads += [PSCustomObject]@{ Speed = $_.SpeedLive SpeedDual = $_.SpeedLiveDual Activity = ($Devices | Where-Object group -eq ($ActiveMiners[$_.IdF].DeviceGroup.GroupName) | Measure-Object -property Utilization -average).average Power = $_.PowerLive Date = (Get-Date).DateTime Benchmarking = $_.NeedBenchmark TimeSinceStartInterval = $TimeSinceStartInterval BenchmarkIntervalTime = $BenchmarkIntervalTime } } if ($_.SpeedReads.Count -gt 2000) { # Remove 10 percent of lowest and highest rate samples which may skew the average $_.SpeedReads = $_.SpeedReads | Sort-Object Speed $p5Index = [math]::Ceiling($_.SpeedReads.Count * 0.05) $p95Index = [math]::Ceiling($_.SpeedReads.Count * 0.95) $_.SpeedReads = $_.SpeedReads[$p5Index..$p95Index] | Sort-Object SpeedDual, Speed $p5Index = [math]::Ceiling($_.SpeedReads.Count * 0.05) $p95Index = [math]::Ceiling($_.SpeedReads.Count * 0.95) $_.SpeedReads = $_.SpeedReads[$p5Index..$p95Index] | Sort-Object Date } if (($Config.LiveStatsUpdate) -eq "Enabled" -or $_.NeedBenchmark) { if ($_.SpeedReads.Count -gt 20 -and $_.NeedBenchmark) { ### If average of last 2 periods is within SpeedDelta, we can stop benchmarking $SpeedDelta = 0.01 $pIndex = [math]::Ceiling($_.SpeedReads.Count * 0.1) $AvgPrev = $_.SpeedReads[($pIndex * 2)..($pIndex * 6)] | Measure-Object -Property Speed -Average | Select-Object -ExpandProperty Average $AvgCurr = $_.SpeedReads[($pIndex * 6)..($_.SpeedReads.count - 1)] | Measure-Object -Property Speed -Average | Select-Object -ExpandProperty Average $AvgPrevDual = $_.SpeedReads[($pIndex * 2)..($pIndex * 6)] | Measure-Object -Property SpeedDual -Average | Select-Object -ExpandProperty Average $AvgCurrDual = $_.SpeedReads[($pIndex * 6)..($_.SpeedReads.count - 1)] | Measure-Object -Property SpeedDual -Average | Select-Object -ExpandProperty Average if ( [math]::Abs($AvgPrev / $AvgCurr - 1) -le $SpeedDelta -and ($AvgPrevDual -eq 0 -or [math]::Abs($AvgPrevDual / $AvgCurrDual - 1) -le $SpeedDelta) ) { $_.SpeedReads = $_.SpeedReads[($pIndex * 2)..($_.SpeedReads.count - 1)] $_.NeedBenchmark = $false } } $Params = @{ Algorithm = $ActiveMiners[$_.IdF].Algorithms MinerName = $ActiveMiners[$_.IdF].Name GroupName = $ActiveMiners[$_.IdF].DeviceGroup.GroupName AlgoLabel = $ActiveMiners[$_.IdF].AlgoLabel PowerLimit = $_.PowerLimit Value = $_.SpeedReads } Set-HashRates @Params } } } if ($Devices) { #WATCHDOG $GroupDevices = @() $GroupDevices += $Devices | Where-Object Group -eq $ActiveMiners[$_.IdF].DeviceGroup.GroupName $ActivityAverages += [PSCustomObject]@{ DeviceGroup = $ActiveMiners[$_.IdF].DeviceGroup.GroupName Average = ($GroupDevices | Measure-Object -property Utilization -average).average NumberOfDevices = $GroupDevices.count } if ($ActivityAverages.count -gt 20 -and ($ActiveMiners.SubMiners | Where-Object Best).count -gt 0) { $ActivityAverages = $ActivityAverages[($ActivityAverages.Count - 20)..($ActivityAverages.Count - 1)] $ActivityAverage = ($ActivityAverages | Where-Object DeviceGroup -eq $ActiveMiners[$_.IdF].DeviceGroup.GroupName | Measure-Object -property Average -maximum).maximum $ActivityDeviceCount = ($ActivityAverages | Where-Object DeviceGroup -eq $ActiveMiners[$_.IdF].DeviceGroup.GroupName | Measure-Object -property NumberOfDevices -maximum).maximum Log-Message "Last 20 reads maximum Device activity is $ActivityAverage for DeviceGroup $($ActiveMiners[$_.IdF].DeviceGroup.GroupName)" -Severity Debug } else { $ActivityAverage = 100 } #only want watchdog works with at least 20 reads } ## HashRate Watchdog $WatchdogHashRateFail = $false if ( $Config.WatchdogHashRate -gt 0 -and ($Config.WatchdogExcludeAlgos -split ',') -notcontains $ActiveMiners[$_.IdF].Algorithm -and $_.HashRate -gt 0 -and $_.SpeedReads.count -gt 20 ) { $AvgCurr = $_.SpeedReads[-10..-1] | Measure-Object -Average -Property Speed | Select-Object -ExpandProperty Average $AvgCurrDual = $_.SpeedReads[-10..-1] | Measure-Object -Average -Property SpeedDual | Select-Object -ExpandProperty Average if ( ($_.HashRate / $AvgCurr - 1) -ge ($Config.WatchdogHashRate / 100) -and (-not $_.HashRateDual -or ($_.HashRateDual / $AvgCurrDual - 1) -ge ($Config.WatchdogHashRate / 100)) ) { # Remove failing SpeedReads from statistics to prevent average skewing $_.SpeedReads = $_.SpeedReads[0..($_.SpeedReads.count - 10)] $WatchdogHashRateFail = $true Log-Message "Detected low hashrate $($ActiveMiners[$_.IdF].Name)/$($ActiveMiners[$_.IdF].Algorithm) : $(ConvertTo-Hash $AvgCurr) vs $(ConvertTo-Hash $_.HashRate)" -Severity Warn } } if ( ($Config.WatchdogHashRate -and $WatchdogHashRateFail) -or $ActiveMiners[$_.IdF].Process -eq $null -or $ActiveMiners[$_.IdF].Process.HasExited -or ($Devices -and $ActivityAverage -le 40 -and $TimeSinceStartInterval -gt 100 -and $ActivityDeviceCount -gt 0) ) { $ExitLoop = $true $_.Status = "PendingCancellation" $_.Stats.FailedTimes++ $_.StatsHistory.FailedTimes++ Log-Message "Detected miner error $($ActiveMiners[$_.IdF].Name)/$($ActiveMiners[$_.IdF].Algorithm) (id $($_.IdF)-$($_.Id)) --> $($ActiveMiners[$_.IdF].Path) $($ActiveMiners[$_.IdF].Arguments)" -Severity Warn } } #End For each ############################################################# if ($Interval.Benchmark -and ($ActiveMiners | Where-Object IsValid | Select-Object -ExpandProperty SubMiners | Where-Object {$_.NeedBenchmark -and $_.Best}).Count -eq 0) { Log-Message "Benchmark completed early" $ExitLoop = $true } #display interval $TimeToNextInterval = New-TimeSpan (Get-Date) ($LoopStartTime.AddSeconds($Interval.Duration)) $TimeToNextIntervalSeconds = [int]$TimeToNextInterval.TotalSeconds if ($TimeToNextIntervalSeconds -lt 0) {$TimeToNextIntervalSeconds = 0} Set-ConsolePosition ($Host.UI.RawUI.WindowSize.Width - 31) 2 " | Next Interval: $TimeToNextIntervalSeconds secs..." | Out-Host Set-ConsolePosition 0 0 #display header Print-HorizontalLine "$Application $Release" Print-HorizontalLine " (E)nd Interval (P)rofits (C)urrent (H)istory (W)allets (S)tats (Q)uit" | Out-Host #display donation message if ($CurrentInterval -eq "Donate") {" THIS INTERVAL YOU ARE DONATING, YOU CAN INCREASE OR DECREASE DONATION ON config.ini, THANK YOU FOR YOUR SUPPORT !!!!"} #write speed Log-Message ($ActiveMiners | Where-Object Best | Select-Object id, process.Id, GroupName, name, poolabbname, Algorithm, AlgorithmDual, SpeedLive, ProfitsLive, location, port, arguments | ConvertTo-Json) -Severity Debug #get pool reported speed (1 or each 10 executions to not saturate pool) if ($SwitchLoop -eq 0) { $CurrentAlgos = ($ActiveMiners.SubMiners | Where-Object Best | ForEach-Object {$ActiveMiners[$_.IdF].Symbol + $(if ($ActiveMiners[$_.IdF].AlgorithmDual) {"_$($ActiveMiners[$_.IdF].SymbolDual)"})}) -join '/' $RunTime = $(Get-Date) - $(Get-Process -Pid $Global:PID | Select-Object -ExpandProperty StartTime) $Host.UI.RawUI.WindowTitle = $(if ($RunTime.TotalDays -lt 1) {"{0:hh\:mm}" -f $RunTime} else {"{0:dd\d\ hh\:mm}" -f $RunTime}) + " : " + $CurrentAlgos # Report stats if ($MinerStatusURL -and $MinerStatusKey) { & .\Includes\ReportStatus.ps1 -Key $MinerStatusKey -WorkerName $WorkerName -ActiveMiners $ActiveMiners -MinerStatusURL $MinerStatusURL } #To get pool speed $PoolsSpeed = @() $Candidates = ($ActiveMiners.SubMiners | Where-Object Best | Select-Object IdF).IdF $ActiveMiners | Where-Object {$Candidates -contains $_.Id} | Select-Object PoolName, UserName, WalletSymbol, Coin, WorkerName -unique | ForEach-Object { $Info = [PSCustomObject]@{ User = $_.UserName PoolName = $_.PoolName ApiKey = $Config.("APIKEY_" + $_.PoolName) Symbol = $_.WalletSymbol Coin = $_.Coin WorkerName = $_.WorkerName } $PoolsSpeed += Get-Pools -Querymode "speed" -PoolsFilterList $_.PoolName -Info $Info } #Dual miners $ActiveMiners | Where-Object {$Candidates -contains $_.Id -and $_.PoolNameDual} | Select-Object PoolNameDual, UserNameDual, WalletSymbol, CoinDual, WorkerName -unique | ForEach-Object { $Info = [PSCustomObject]@{ User = $_.UserNameDual PoolName = $_.PoolNameDual ApiKey = $Config.("APIKEY_" + $_.PoolNameDual) Symbol = $_.WalletSymbol Coin = $_.CoinDual WorkerName = $_.WorkerNameDual } $PoolsSpeed += Get-Pools -Querymode "speed" -PoolsFilterList $_.PoolNameDual -Info $Info } foreach ($Candidate in $Candidates) { $Me = $PoolsSpeed | Where-Object {$_.PoolName -eq $ActiveMiners[$Candidate].PoolName -and $_.WorkerName -eq $ActiveMiners[$Candidate].WorkerName } | Select-Object HashRate, PoolName, WorkerName -first 1 $ActiveMiners[$Candidate].PoolHashRate = $Me.HashRate $MeDual = $PoolsSpeed | Where-Object {$_.PoolName -eq $ActiveMiners[$Candidate].PoolNameDual -and $_.WorkerName -eq $ActiveMiners[$Candidate].WorkerNameDual} | Select-Object HashRate, PoolName, WorkerName -first 1 $ActiveMiners[$Candidate].PoolHashRateDual = $MeDual.HashRate } } $SwitchLoop++ if ($SwitchLoop -gt 5) {$SwitchLoop = 0} #reduces 10-1 ratio of execution #display current mining info Print-HorizontalLine $ScreenOut = $ActiveMiners.Subminers | Where-Object Best | Sort-Object ` @{expression = {$ActiveMiners[$_.IdF].DeviceGroup.GroupName -eq 'CPU'}; Ascending = $true}, @{expression = {$ActiveMiners[$_.IdF].DeviceGroup.GroupName}; Ascending = $true} | ForEach-Object { [PSCustomObject]@{ GroupName = $ActiveMiners[$_.IdF].DeviceGroup.GroupName PwLim = if ($_.PowerLimit -ne 0) {$_.PowerLimit} else {""} LocalSpeed = "$(ConvertTo-Hash $_.SpeedLive)" + $(if ($ActiveMiners[$_.IdF].AlgorithmDual) {"/$(ConvertTo-Hash $_.SpeedLiveDual)"}) mbtcDay = ((($_.RevenueLive + $_.RevenueLiveDual) * 1000).tostring("n5")) RevDay = ((($_.RevenueLive + $_.RevenueLiveDual) * $localBTCvalue ).tostring("n2")) ProfitDay = (($_.ProfitsLive).tostring("n2")) Algorithm = $ActiveMiners[$_.IdF].Algorithms + $(if ($ActiveMiners[$_.IdF].AlgoLabel) {'|' + $ActiveMiners[$_.IdF].AlgoLabel}) + $_.BestBySwitch Coin = $ActiveMiners[$_.IdF].Symbol + $(if ($ActiveMiners[$_.IdF].AlgorithmDual) {"_$($ActiveMiners[$_.IdF].SymbolDual)"}) Miner = $ActiveMiners[$_.IdF].Name Power = [string]$_.PowerLive + 'W' EfficiencyH = if (!($ActiveMiners[$_.IdF].AlgorithmDual) -and $_.PowerLive -gt 0) {ConvertTo-Hash ($_.SpeedLive / $_.PowerLive)} else {$null} EfficiencyW = if ($_.PowerLive -gt 0) {($_.ProfitsLive / $_.PowerLive).tostring("n4")} else {$null} PoolSpeed = "$(ConvertTo-Hash $ActiveMiners[$_.IdF].PoolHashRate)" + $(if ($ActiveMiners[$_.IdF].AlgorithmDual) {"/$(ConvertTo-Hash $ActiveMiners[$_.IdF].PoolHashRateDual)"}) Pool = $ActiveMiners[$_.IdF].PoolAbbName + $(if ($ActiveMiners[$_.IdF].AlgorithmDual) {"|$($ActiveMiners[$_.IdF].PoolAbbNameDual)"}) Workers = $ActiveMiners[$_.IdF].PoolWorkers Location = $ActiveMiners[$_.IdF].Location } } if ($ScreenOut) { $ScreenOut | Format-Table ( @{Label = "Group"; Expression = {$_.GroupName}}, @{Label = "Algorithm"; Expression = {$_.Algorithm}}, @{Label = "Coin"; Expression = {$_.Coin}}, @{Label = "Miner"; Expression = {$_.Miner}}, @{Label = "LocalSpeed"; Expression = {$_.LocalSpeed} ; Align = 'right'}, @{Label = "PwLim"; Expression = {$_.PwLim} ; Align = 'right'}, @{Label = "Watt"; Expression = {$_.Power} ; Align = 'right'}, @{Label = "$LocalCurrency/W"; Expression = {$_.EfficiencyW} ; Align = 'right'}, @{Label = "mBTC/Day"; Expression = {$_.mbtcDay} ; Align = 'right'}, @{Label = "$LocalCurrency/Day"; Expression = {$_.RevDay} ; Align = 'right'}, @{Label = "Profit/Day"; Expression = {$_.ProfitDay} ; Align = 'right'}, # @{Label = "Hash/W"; Expression = {$_.EfficiencyH} ; Align = 'right'}, @{Label = "PoolSpeed"; Expression = {$_.PoolSpeed} ; Align = 'right'}, @{Label = "Pool"; Expression = {$_.Pool} ; Align = 'right'}, @{Label = "Workers"; Expression = {$_.Workers} ; Align = 'right'}, @{Label = "Loc."; Expression = {$_.Location} ; Align = 'right'} ) | Out-Host } else { Write-Warning "No miners above MinProfit" } if ($config.ApiPort -gt 0) { #generate api response $ApiResponse = [PSCustomObject]@{} $ApiResponse | Add-Member ActiveMiners $ScreenOut $ApiResponse | Add-Member Config $config $ApiResponse | Add-Member Params ([PSCustomObject]@{}) $ApiResponse.Params | Add-Member Algorithms $Algorithm $ApiResponse.Params | Add-Member Pools $PoolsName $ApiResponse.Params | Add-Member Coins $CoinsName $ApiResponse.Params | Add-Member MiningMode $MiningMode $ApiResponse.Params | Add-Member GroupNames $GroupNames $ApiResponse | Add-Member Release $Release $ApiResponse | Add-Member RefreshDate ((Get-Date).tostring("o")) $ApiResponse | ConvertTo-Json | Set-Content -path $ApiSharedFile } $XToWrite = [ref]0 $YToWrite = [ref]0 Get-ConsolePosition ([ref]$XToWrite) ([ref]$YToWrite) $YToWriteMessages = $YToWrite + 1 $YToWriteData = $YToWrite + 2 Remove-Variable XToWrite Remove-Variable YToWrite ############################################################# Print-HorizontalLine $Screen.ToUpper() #display profits screen if ($Screen -eq "Profits" -and $RepaintScreen) { Set-ConsolePosition ($Host.UI.RawUI.WindowSize.Width - 37) $YToWriteMessages "(B)est Miners/All (T)op " + [string]$InitialProfitsScreenLimit + "/All" | Out-Host Set-ConsolePosition 0 $YToWriteData $ProfitMiners = @() if ($ShowBestMinersOnly) { foreach ($SubMiner in ($ActiveMiners.SubMiners | Where-Object {$ActiveMiners[$_.IdF].IsValid -and $_.Status -ne 'Failed'})) { $Candidates = $ActiveMiners | Where-Object {$_.IsValid -and $_.DeviceGroup.Id -eq $ActiveMiners[$SubMiner.IdF].DeviceGroup.Id -and $_.Algorithm -eq $ActiveMiners[$SubMiner.IdF].Algorithm -and $_.AlgorithmDual -eq $ActiveMiners[$SubMiner.IdF].AlgorithmDual } $ExistsBest = $Candidates.SubMiners | Where-Object {$_.Profits -gt $SubMiner.Profits} if ($null -eq $ExistsBest -and 0 -eq $SubMiner.Profits) { $ExistsBest = $Candidates | Where-Object {$_.HashRate -gt $SubMiner.HashRate} } if ($null -eq $ExistsBest -or $true -eq $SubMiner.NeedBenchmark) { $ProfitMiner = $ActiveMiners[$SubMiner.IdF] | Select-Object * -ExcludeProperty SubMiners $ProfitMiner | Add-Member SubMiner $SubMiner $ProfitMiner | Add-Member GroupName $ProfitMiner.DeviceGroup.GroupName #needed for groupby $ProfitMiner | Add-Member NeedBenchmark $ProfitMiner.SubMiner.NeedBenchmark #needed for sort $ProfitMiner | Add-Member Profits $ProfitMiner.SubMiner.Profits #needed for sort $ProfitMiner | Add-Member Status $ProfitMiner.SubMiner.Status #needed for sort $ProfitMiners += $ProfitMiner } } } else { $ActiveMiners.SubMiners | Where-Object {$ActiveMiners[$_.IdF].IsValid} | ForEach-Object { $ProfitMiner = $ActiveMiners[$_.IdF] | Select-Object * -ExcludeProperty SubMiners $ProfitMiner | Add-Member SubMiner $_ $ProfitMiner | Add-Member GroupName $ProfitMiner.DeviceGroup.GroupName #needed for groupby $ProfitMiner | Add-Member NeedBenchmark $ProfitMiner.SubMiner.NeedBenchmark #needed for sort $ProfitMiner | Add-Member Profits $ProfitMiner.SubMiner.Profits #needed for sort $ProfitMiner | Add-Member Status $ProfitMiner.SubMiner.Status #needed for sort $ProfitMiners += $ProfitMiner } } $ProfitMiners2 = @() foreach ($DeviceGroupId in $DeviceGroups.Id) { $inserted = 1 $ProfitMiners | Where-Object {$_.DeviceGroup.Id -eq $DeviceGroupId} | Sort-Object -Descending GroupName, NeedBenchmark, Profits | ForEach-Object { if ($inserted -le $ProfitsScreenLimit) {$ProfitMiners2 += $_; $inserted++} #this can be done with Select-Object -first but then memory leak happens, ¿why? } } #Display profits information $ProfitMiners2 | Sort-Object ` @{expression = {$_.GroupName -eq 'CPU'}; Ascending = $true}, @{expression = "GroupName"; Ascending = $true}, @{expression = "Status"; Descending = $true}, @{expression = "NeedBenchmark"; Descending = $true}, @{expression = "Profits"; Descending = $true}, @{expression = "HashRate"; Descending = $true}, @{expression = "HashRateDual"; Descending = $true} | Format-Table ( @{Label = "Algorithm"; Expression = {$_.Algorithms + $(if ($_.AlgoLabel) {"|$($_.AlgoLabel)"})}}, @{Label = "Coin"; Expression = {$_.Symbol + $(if ($_.AlgorithmDual) {"_$($_.SymbolDual)"})}}, @{Label = "Miner"; Expression = {$_.Name}}, @{Label = "StatsSpeed"; Expression = {if ($_.SubMiner.NeedBenchmark) {"Benchmarking"} else {"$(ConvertTo-Hash $_.SubMiner.HashRate)" + $(if ($_.AlgorithmDual) {"/$(ConvertTo-Hash $_.SubMiner.HashRateDual)"})}}; Align = 'right'}, @{Label = "PwLim"; Expression = {if ($_.SubMiner.PowerLimit -ne 0) {$_.SubMiner.PowerLimit}}; align = 'right'}, @{Label = "Watt"; Expression = {if ($_.SubMiner.PowerAvg -gt 0) {$_.SubMiner.PowerAvg.tostring("n0")} else {$null}}; Align = 'right'}, @{Label = "$LocalCurrency/W"; Expression = {if ($_.SubMiner.PowerAvg -gt 0) {($_.SubMiner.Profits / $_.SubMiner.PowerAvg).tostring("n4")} else {$null} }; Align = 'right'}, @{Label = "mBTC/Day"; Expression = {if ($_.SubMiner.Revenue) {((($_.SubMiner.Revenue + $_.SubMiner.RevenueDual) * 1000).tostring("n5"))} else {$null}} ; Align = 'right'}, @{Label = $LocalCurrency + "/Day"; Expression = {if ($_.SubMiner.Revenue) {((($_.SubMiner.Revenue + $_.SubMiner.RevenueDual) * [double]$localBTCvalue).tostring("n2"))} else {$null}} ; Align = 'right'}, @{Label = "Profit/Day"; Expression = {if ($_.SubMiner.Profits) {($_.SubMiner.Profits).tostring("n2") + " $LocalCurrency"} else {$null}}; Align = 'right'}, @{Label = "PoolFee"; Expression = {if ($_.PoolFee -gt 0) {"{0:p2}" -f $_.PoolFee}}; Align = 'right'}, @{Label = "MinerFee"; Expression = {if ($_.MinerFee -gt 0) {"{0:p2}" -f $_.MinerFee}}; Align = 'right'}, @{Label = "Pool"; Expression = {$_.PoolAbbName + $(if ($_.AlgorithmDual) {"/$($_.PoolAbbNameDual)"})}}, @{Label = "Loc."; Expression = {$_.Location}} ) -GroupBy GroupName | Out-Host Remove-Variable ProfitMiners Remove-Variable ProfitMiners2 $RepaintScreen = $false } if ($Screen -eq "Current") { Set-ConsolePosition 0 $YToWriteData # Display devices info Print-DevicesInformation $Devices } ############################################################# if ( ($Screen -eq "Wallets" -or -not $WalletStatusAtStart) -and -not $ExitLoop ) { if (-not $WalletsUpdate) { #wallets only refresh for manual request $WalletsUpdate = Get-Date $WalletsToCheck = @() $WalletsToCheck += $AllPools | Where-Object {$_.WalletMode -eq 'WALLET' -and $_.User} | Select-Object PoolName, User, WalletMode, WalletSymbol -unique | ForEach-Object { [PSCustomObject]@{ PoolName = $_.PoolName WalletMode = $_.WalletMode User = $_.User Coin = $null Algorithm = $null Symbol = $_.WalletSymbol } } $WalletsToCheck += $AllPools | Where-Object {$_.WalletMode -eq 'APIKEY' -and $Config.("APIKEY_" + $_.PoolName)} | Select-Object PoolName, Algorithm, WalletMode, WalletSymbol, @{Name = "ApiKey"; Expression = {$Config.("APIKEY_" + $_.PoolName)}} -unique | ForEach-Object { [PSCustomObject]@{ PoolName = $_.PoolName WalletMode = $_.WalletMode User = $null Algorithm = $_.Algorithm Symbol = $_.WalletSymbol ApiKey = $_.ApiKey } } $WalletStatus = @() $WalletsToCheck | ForEach-Object { Set-ConsolePosition 0 $YToWriteMessages " " * 70 | Out-Host Set-ConsolePosition 0 $YToWriteMessages if ($_.WalletMode -eq "WALLET") {Log-Message "Checking $($_.PoolName) - $($_.Symbol)"} else {Log-Message "Checking $($_.PoolName) - $($_.Symbol) ($($_.Algorithm))"} $Ws = Get-Pools -Querymode $_.WalletMode -PoolsFilterList $_.PoolName -Info ($_) if ($_.WalletMode -eq "WALLET") {$Ws | Add-Member Wallet $_.User} else {$Ws | Add-Member Wallet $_.Coin} $Ws | Add-Member PoolName $_.PoolName $Ws | Add-Member WalletSymbol $_.Symbol $WalletStatus += $Ws } -End { Set-ConsolePosition 0 $YToWriteMessages " " * 70 | Out-Host } if (-not $WalletStatusAtStart) {$WalletStatusAtStart = $WalletStatus} foreach ($Wallet in $WalletStatus) { if (-not $Wallet.BalanceAtStart) { $BalanceAtStart = $WalletStatusAtStart | Where-Object { $_.Wallet -eq $Wallet.Wallet -and $_.PoolName -eq $Wallet.PoolName -and $_.Currency -eq $Wallet.Currency } | Select-Object -ExpandProperty Balance if ($BalanceAtStart) { $Wallet | Add-Member BalanceAtStart $BalanceAtStart } else { $WalletStatusAtStart += $Wallet } } } } if ($Screen -eq "Wallets" -and $RepaintScreen) { Set-ConsolePosition 0 $YToWriteMessages "Start Time: $((Get-Process -PID $PID).StartTime) " Set-ConsolePosition ($Host.UI.RawUI.WindowSize.Width - 10) $YToWriteMessages "(U)pdate" | Out-Host "" | Out-Host $WalletStatus | Where-Object Balance | Sort-Object @{expression = "PoolName"; Ascending = $true}, @{expression = "balance"; Descending = $true} | Format-Table -Wrap -groupby PoolName ( @{Label = "Coin"; Expression = {if ($_.WalletSymbol -ne $null) {$_.WalletSymbol} else {$_.wallet}}}, @{Label = "Balance"; Expression = {$_.Balance.tostring("n5")}; Align = 'right'}, @{Label = "IncFromStart"; Expression = {($_.Balance - $_.BalanceAtStart).tostring("n5")}; Align = 'right'} ) | Out-Host $Pools | Where-Object WalletMode -eq 'NONE' | Select-Object PoolName -unique | ForEach-Object { "NO API FOR POOL " + $_.PoolName + " - NO WALLETS CHECK" | Out-Host } $RepaintScreen = $false } } ############################################################# if ($Screen -eq "History" -and $RepaintScreen) { Set-ConsolePosition 0 $YToWriteMessages "Running Mode: $MiningMode" | Out-Host Set-ConsolePosition 0 $YToWriteData #Display activated miners list $ActiveMiners.SubMiners | Where-Object {$_.Stats.ActivatedTimes -gt 0} | Sort-Object ` @{expression = {$ActiveMiners[$_.IdF].DeviceGroup.GroupName -eq 'CPU'}; Ascending = $true}, @{expression = {$ActiveMiners[$_.IdF].DeviceGroup.GroupName}; Ascending = $true}, @{expression = {$_.Stats.LastTimeActive}; Descending = $true} | Format-Table -Wrap -GroupBy @{Label = "Group"; Expression = {$ActiveMiners[$_.IdF].DeviceGroup.GroupName}} ( @{Label = "LastTimeActive"; Expression = {$($_.Stats.LastTimeActive).tostring("dd/MM/yy H:mm")}}, @{Label = "Command"; Expression = {"$($ActiveMiners[$_.IdF].Path.TrimStart((Convert-Path ".\Bin\"))) $($ActiveMiners[$_.IdF].Arguments)"}} ) | Out-Host $RepaintScreen = $false } ############################################################# if ($Screen -eq "Stats" -and $RepaintScreen) { Set-ConsolePosition 0 $YToWriteMessages "Start Time: $((Get-Process -PID $PID).StartTime)" Set-ConsolePosition ($Host.UI.RawUI.WindowSize.Width - 30) $YToWriteMessages "Running Mode: $MiningMode" | Out-Host Set-ConsolePosition 0 $YToWriteData #Display activated miners list $ActiveMiners.SubMiners | Where-Object {$_.Stats.ActivatedTimes -gt 0} | Sort-Object ` @{expression = {$ActiveMiners[$_.IdF].DeviceGroup.GroupName -eq 'CPU'}; Ascending = $true}, @{expression = {$ActiveMiners[$_.IdF].DeviceGroup.GroupName}; Ascending = $true}, @{expression = {$_.Stats.Activetime}; Descending = $true} | Format-Table -Wrap -GroupBy @{Label = "Group"; Expression = {$ActiveMiners[$_.IdF].DeviceGroup.GroupName}}( @{Label = "Algorithm"; Expression = {$ActiveMiners[$_.IdF].Algorithms + $(if ($ActiveMiners[$_.IdF].AlgoLabel) {"|$($ActiveMiners[$_.IdF].AlgoLabel)"})}}, @{Label = "Coin"; Expression = {$ActiveMiners[$_.IdF].Symbol + $(if ($ActiveMiners[$_.IdF].AlgorithmDual) {"_$($ActiveMiners[$_.IdF].SymbolDual)"})}}, @{Label = "Pool"; Expression = {$ActiveMiners[$_.IdF].PoolAbbName + $(if ($ActiveMiners[$_.IdF].AlgorithmDual) {"/$($ActiveMiners[$_.IdF].PoolAbbNameDual)"})}}, @{Label = "Miner"; Expression = {$ActiveMiners[$_.IdF].Name}}, @{Label = "PwLmt"; Expression = {if ($_.PowerLimit -gt 0) {$_.PowerLimit}}}, @{Label = "Launch"; Expression = {$_.Stats.ActivatedTimes}}, @{Label = "Best"; Expression = {$_.Stats.BestTimes}}, @{Label = "ActiveTime"; Expression = {if ($_.Stats.ActiveTime -le 3600) {"{0:N1} min" -f ($_.Stats.ActiveTime / 60)} else {"{0:N1} hours" -f ($_.Stats.ActiveTime / 3600)}}}, @{Label = "LastTimeActive"; Expression = {$($_.Stats.LastTimeActive).tostring("dd/MM/yy H:mm")}}, @{Label = "Status"; Expression = {$_.Status}} ) | Out-Host } $FirstLoopExecution = $false #Loop for reading key and wait $KeyPressed = Read-KeyboardTimed 3 ('P', 'C', 'H', 'E', 'W', 'U', 'T', 'B', 'S', 'X', 'Q') switch ($KeyPressed) { 'P' {$Screen = 'PROFITS'} 'C' {$Screen = 'CURRENT'} 'H' {$Screen = 'HISTORY'} 'S' {$Screen = 'STATS'} 'E' {$ExitLoop = $true; Log-Message "Forced end of interval by E key"} 'W' {$Screen = 'WALLETS'} 'U' {if ($Screen -eq "WALLETS") {$WalletsUpdate = $null}} 'T' {if ($Screen -eq "PROFITS") {if ($ProfitsScreenLimit -eq $InitialProfitsScreenLimit) {$ProfitsScreenLimit = 1000} else {$ProfitsScreenLimit = $InitialProfitsScreenLimit}}} 'B' {if ($Screen -eq "PROFITS") {$ShowBestMinersOnly = !$ShowBestMinersOnly}} 'X' {try {Set-WindowSize 180 50} catch {}} 'Q' {$Quit = $true; $ExitLoop = $true} } if ($KeyPressed) {Clear-Host; $RepaintScreen = $true} if (((Get-Date) -ge ($LoopStartTime.AddSeconds($Interval.Duration))) ) { #If time of interval has over, Exit of main loop #If last interval was benchmark and no speed detected mark as failed $ActiveMiners.SubMiners | Where-Object Best | ForEach-Object { if ($_.NeedBenchmark -and $_.SpeedReads.Count -eq 0) { $_.Status = 'PendingCancellation' $_.Stats.FailedTimes++ Log-Message "No speed detected while benchmark $($ActiveMiners[$_.IdF].Name)/$($ActiveMiners[$_.IdF].Algorithm) (id $($ActiveMiners[$_.IdF].Id))" -Severity Warn } } $ExitLoop = $true Log-Message "Interval ends by time: $($Interval.Duration)" -Severity Debug } if ($ExitLoop) {break} #forced Exit Send-ErrorsToLog $LogFile } Remove-Variable miners Remove-Variable pools Get-Job -State Completed | Remove-Job [GC]::Collect() #force garbage collector for free memory } #----------------------------------------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------------------------------------- #-------------------------------------------end of always running loop-------------------------------------------------- #----------------------------------------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------------------------------------- Log-Message "Exiting Forager" $LogFile.close() Clear-Files $ActiveMiners | Where-Object Process -ne $null | ForEach-Object {try {Exit-Process $_.Process} catch {}} try {Invoke-WebRequest ("http://localhost:$($config.ApiPort)/?command=Exit") -timeoutsec 1 -UseDefaultCredentials} catch {} if ($EthPill -ne $null) {Stop-Process -Id $EthPill.Id} Stop-Process -Id $PID