From 9feab3fe2caa883efc20c3309cf358097c1fddf3 Mon Sep 17 00:00:00 2001 From: MrPlusGH <36303784+MrPlusGH@users.noreply.github.com> Date: Tue, 19 May 2020 14:39:44 +0200 Subject: [PATCH] 7.4.2 --- .gitattributes | 1 + .gitignore | 4 +- Config/config.json | 22 +- Includes/Algorithms.txt | 6 +- Includes/Charting.ps1 | 38 +- Includes/Core.ps1 | 596 ++++++++++-------- Includes/EarningsTracker.ps1 | 301 +++++++++ Includes/EarningsTrackerJob.ps1 | 64 +- Includes/Include.ps1 | 387 +++++++++--- Includes/MinerCustomConfig.ps1 | 51 +- Includes/MinerCustomConfigEditor.ps1 | 4 +- Includes/Server.ps1 | 872 +++++++++++++++++++++++++++ Includes/Web.css | 93 +++ Logs/CoinIcons.json | Bin 0 -> 42818 bytes NPlusMiner.ps1 | 344 +++++++++-- Pools/ahashpool.ps1 | 51 ++ Pools/ahashpool24hr.ps1 | 51 ++ Pools/ahashpoolplus.ps1 | 57 ++ Pools/blazepool.ps1 | 51 ++ Pools/blazepool24hr.ps1 | 51 ++ Pools/blazepoolplus.ps1 | 57 ++ Pools/blockmasters.ps1 | 61 ++ Pools/blockmasters24hr.ps1 | 62 ++ Pools/blockmastersplus.ps1 | 70 +++ Pools/miningpoolhub.ps1 | 65 ++ Pools/nicehash.ps1 | 79 +++ Pools/nlpool.ps1 | 57 ++ Pools/nlpool24hr.ps1 | 57 ++ Pools/nlpoolplus.ps1 | 62 ++ Pools/prohashing.ps1 | 59 ++ Pools/prohashing24hr.ps1 | 59 ++ Pools/prohashingplus.ps1 | 66 ++ Pools/zergpool.ps1 | 51 ++ Pools/zergpool24hr.ps1 | 51 ++ Pools/zergpoolplus.ps1 | 59 ++ Pools/zpool.ps1 | 32 +- Pools/zpool24hr.ps1 | 28 +- Pools/zpoolplus.ps1 | 90 +-- README.md | 252 +++++++- Utils/API-Mine.ps1 | 14 + Utils/API-Pause.ps1 | 14 + Utils/WebUI.png | Bin 0 -> 89875 bytes version.json | 2 +- 43 files changed, 3876 insertions(+), 515 deletions(-) create mode 100644 .gitattributes create mode 100644 Includes/EarningsTracker.ps1 create mode 100644 Includes/Server.ps1 create mode 100644 Includes/Web.css create mode 100644 Logs/CoinIcons.json create mode 100644 Pools/ahashpool.ps1 create mode 100644 Pools/ahashpool24hr.ps1 create mode 100644 Pools/ahashpoolplus.ps1 create mode 100644 Pools/blazepool.ps1 create mode 100644 Pools/blazepool24hr.ps1 create mode 100644 Pools/blazepoolplus.ps1 create mode 100644 Pools/blockmasters.ps1 create mode 100644 Pools/blockmasters24hr.ps1 create mode 100644 Pools/blockmastersplus.ps1 create mode 100644 Pools/miningpoolhub.ps1 create mode 100644 Pools/nicehash.ps1 create mode 100644 Pools/nlpool.ps1 create mode 100644 Pools/nlpool24hr.ps1 create mode 100644 Pools/nlpoolplus.ps1 create mode 100644 Pools/prohashing.ps1 create mode 100644 Pools/prohashing24hr.ps1 create mode 100644 Pools/prohashingplus.ps1 create mode 100644 Pools/zergpool.ps1 create mode 100644 Pools/zergpool24hr.ps1 create mode 100644 Pools/zergpoolplus.ps1 create mode 100644 Utils/API-Mine.ps1 create mode 100644 Utils/API-Pause.ps1 create mode 100644 Utils/WebUI.png diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..efd645d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +Includes/NPM.gif export-ignore diff --git a/.gitignore b/.gitignore index e83e7c2..f7a0f94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ Bin/* Stats/* -Logs/* *.rar *.7z -*.zip \ No newline at end of file +*.zip +Includes/NPM.gif diff --git a/Config/config.json b/Config/config.json index 0d1f56b..ac9c3fc 100644 --- a/Config/config.json +++ b/Config/config.json @@ -11,13 +11,13 @@ "Currency": "USD", "Delay": 1, "DisableGPU0": false, - "Donate": 8, + "Donate": 16, "EnableEarningsTrackerLogs": false, "FirstInterval": 30, "HideConsole": true, "IdleSec": "15", "IncludeOptionalMiners": false, - "Interval": 240, + "Interval": 300, "IsFixedSize": false, "IsReadOnly": false, "IsSynchronized": true, @@ -37,20 +37,30 @@ "Passwordcurrency": "BTC", "PenalizeSoloInPlus": true, "PoolName": [ - "nicehash", - "miningpoolhub", - "zergpoolplus" + "zpoolplus" ], "Proxy": "", "ReportToServer": false, "SelGPUCC": "0", "SelGPUDSTM": "0", + "Server_Client": true, + "Server_ClientIP": "127.0.0.1", + "Server_ClientPassword": "3890292d-990c-44ba-b779-552829fc0bc4", + "Server_ClientPort": "4028", + "Server_ClientUser": "NPlusMiner", + "Server_Log": false, + "Server_On": true, + "Server_Password": "3890292d-990c-44ba-b779-552829fc0bc4", + "Server_Port": "4028", + "Server_ServerProxyTimeOut": 1, + "Server_Standalone": false, + "Server_User": "NPlusMiner", "ShowOnlyTopCoins": true, "ShowWorkerStatus": false, "SSL": false, "StartGUIMinimized": false, "StartPaused": false, - "StatsInterval": 180, + "StatsInterval": 60, "SyncRoot": { }, diff --git a/Includes/Algorithms.txt b/Includes/Algorithms.txt index 3b5afa4..d977080 100644 --- a/Includes/Algorithms.txt +++ b/Includes/Algorithms.txt @@ -110,10 +110,12 @@ "equihash": "Equihash", "exosis": "Exosis", "equihash192": "Equihash192", + "EquihashZcl": "Equihash192", + "Equihash1254": "Equihash125", "equihash144": "Equihash144", + "Equihash1445": "Equihash144", + "EquihashBTG": "Equihash144", "equihash96": "Equihash96", - "equihash-btg": "EquihashBTG", - "equihashbtg": "EquihashBTG", "Espers": "HMQ1725", "eth": "Ethash", "ethash": "Ethash", diff --git a/Includes/Charting.ps1 b/Includes/Charting.ps1 index 9f72a8a..b7cd667 100644 --- a/Includes/Charting.ps1 +++ b/Includes/Charting.ps1 @@ -30,7 +30,9 @@ param( [Parameter(Mandatory=$true)] [String]$Width = 700, [Parameter(Mandatory = $true)] - [String]$Height = 85 + [String]$Height = 85, + [Parameter(Mandatory = $false)] + [String]$Currency = "BTC" ) Function GetNextColor { @@ -115,8 +117,11 @@ Switch ($Chart) { # $BaseColor = "424B54" $BaseColor = "FFFFFF" # $BaseColor = "F7931A" - $Color = $BaseColor - $A=255 + $StartColor = "FFFFFF" + $EndColor = "f2a900" + $i=0 + # $Colors = Get-ColorPalette $StartColor $EndColor (($datasource | sort DaySum -Unique).DaySum | % {[math]::Round($_*1000, 3)} | sort -Unique).count + $Colors = Get-ColorPalette $StartColor $EndColor 100 [void]$chart1.Series.Add("Total") $chart1.Series["Total"].ChartType = "Column" @@ -129,11 +134,17 @@ Switch ($Chart) { # $chart1.Series[$Pool].color = [System.Drawing.Color]::FromArgb($A,247,147,26) $chart1.Series["Total"].label = "#VALY{N3}" $chart1.Series["Total"].LabelForeColor = "#FFFFFF" - $chart1.Series["Total"].ToolTip = "#VALX: #VALY mBTC" # - Total: #TOTAL mBTC"; + $chart1.Series["Total"].ToolTip = "#VALX: #VALY" # - Total: #TOTAL mBTC"; # $datasource | select Date,DaySum -Unique | ForEach-Object {$chart1.Series["Total"].Points.addxy( $_.Date , ("{0:N3}" -f ([Decimal]$_.DaySUm*1000))) | Out-Null } - $datasource | select Date,DaySum -Unique | ForEach-Object {$chart1.Series["Total"].Points.addxy( $_.Date , (([Decimal]$_.DaySUm*1000))) | Out-Null } + $datasource | select Date,DaySum -Unique | ForEach-Object {$chart1.Series["Total"].Points.addxy( $_.Date , (([Decimal](Get-DisplayCurrency $_.DaySum).Value))) | Out-Null } $Chart1.Series | foreach {$_.CustomProperties = "DrawSideBySide=True"} + Try{ + $Chart1.Series["Total"].Points | ForEach-Object { + # $PSItem.Color = "#$($Colors[((($datasource | sort DaySum -Unique).DaySum | % {[math]::Round($_*1000, 3)} | sort -Unique)).IndexOf([math]::Round(($PSItem.YValues[0]), 3))])" + $PSItem.Color = "#$($Colors[[Int](100 * ($PSItem.YValues[0]) / (($datasource | group date | % {($_.group.DailyEarnings | measure -sum).sum} | measure -maximum).maximum * 1000))])" + } + } Catch {} } "Front7DaysEarningsWithPoolSplit" { $datasource = If (Test-Path ".\logs\DailyEarnings.csv" ) {Import-Csv ".\logs\DailyEarnings.csv" | ? {[DateTime]$_.date -ge (Get-Date).AddDays(-7)}} @@ -186,8 +197,8 @@ Switch ($Chart) { $chart1.Series[$Pool].color = "#$($Color)" # $chart1.Series[$Pool].color = [System.Drawing.Color]::FromArgb($A,247,147,26) # $chart1.Series[$Pool].label = "#VALY" - $chart1.Series[$Pool].ToolTip = "#SERIESNAME: #VALY mBTC" # - Total: #TOTAL mBTC"; - $datasource | ? {$_.Pool -eq $Pool} | ForEach-Object {$chart1.Series[$Pool].Points.addxy( $_.Date , ("{0:N3}" -f ([Decimal]$_.DailyEarnings*1000))) | Out-Null } + $chart1.Series[$Pool].ToolTip = "#SERIESNAME: #VALY" # - Total: #TOTAL mBTC"; + $datasource | ? {$_.Pool -eq $Pool} | ForEach-Object {$chart1.Series[$Pool].Points.addxy( $_.Date , ("{0:N3}" -f (([Decimal](Get-DisplayCurrency $_.DailyEarnings).Value)))) | Out-Null } } [void]$chart1.Series.Add("Total") @@ -200,13 +211,14 @@ Switch ($Chart) { $chart1.Series["Total"].color = "#FFFFFF" # $chart1.Series[$Pool].color = [System.Drawing.Color]::FromArgb($A,247,147,26) # $chart1.Series[$Pool].label = "#VALY" - $chart1.Series["Total"].ToolTip = "#SERIESNAME: #VALY mBTC" # - Total: #TOTAL mBTC"; - $datasource | select Date,DaySum -Unique | ForEach-Object {$chart1.Series[$Pool].Points.addxy( $_.Date , ("{0:N3}" -f ([Decimal]$_.DaySUm*1000))) | Out-Null } + $chart1.Series["Total"].ToolTip = "#SERIESNAME: #VALY" # - Total: #TOTAL mBTC"; + $datasource | select Date,DaySum -Unique | ForEach-Object {$chart1.Series[$Pool].Points.addxy( $_.Date , ("{0:N3}" -f (([Decimal](Get-DisplayCurrency $_.DailyEarnings).Value)))) | Out-Null } $Chart1.Series | foreach {$_.CustomProperties = "DrawSideBySide=True"} } "DayPoolSplit" { - $datasource = If (Test-Path ".\logs\DailyEarnings.csv" ) {Import-Csv ".\logs\DailyEarnings.csv" | ? {$_.date -ge (Get-Date).date.AddDays(-1).ToString("MM/dd/yyyy")}} + $datasource = If (Test-Path ".\logs\DailyEarnings.csv" ) {Import-Csv ".\logs\DailyEarnings.csv" | ? {[datetime]$_.date -ge [datetime](Get-Date).date.AddDays(-1).ToString("MM/dd/yyyy")}} + # $datasource = If (Test-Path ".\logs\DailyEarnings.csv" ) {Import-Csv ".\logs\DailyEarnings.csv" } $dataSource | % {$_.DailyEarnings = [Decimal]$_.DailyEarnings} $datasource = $dataSource | ? {$_.DailyEarnings -gt 0} | sort DailyEarnings #-Descending @@ -263,13 +275,13 @@ Switch ($Chart) { # $chart1.Series[$Pool].color = "#FFFFFF" # $chart1.Series[$Pool].color = [System.Drawing.Color]::FromArgb($A,247,147,26) # $chart1.Series[$Pool].label = "#SERIESNAME: #VALY mBTC" - $chart1.Series[$Pool].ToolTip = "#VALX - #SERIESNAME: #VALY mBTC" # - Total: #TOTAL mBTC"; - $datasource | ? {$_.Pool -eq $Pool} | Sort date | ForEach-Object {$chart1.Series[$Pool].Points.addxy( $_.Date , ("{0:N3}" -f ([Decimal]$_.DailyEarnings*1000))) | Out-Null } + $chart1.Series[$Pool].ToolTip = "#VALX - #SERIESNAME: #VALY" # - Total: #TOTAL mBTC"; + $datasource | ? {$_.Pool -eq $Pool} | Sort date | ForEach-Object {$chart1.Series[$Pool].Points.addxy( $_.Date , ("{0:N3}" -f (([Decimal](Get-DisplayCurrency $_.DailyEarnings).Value)))) | Out-Null } # $Chart1.Series["Data"].Points.DataBindXY($datasource.pool, $datasource.DailyEarnings) } } } # save chart - # $chart1.SaveImage("$scriptpath\ChartTest.png","png") | Out-Null + $chart1.SaveImage(".\Logs\$($chart).png","png") | Out-Null $chart1 diff --git a/Includes/Core.ps1 b/Includes/Core.ps1 index f38b656..5f36b75 100644 --- a/Includes/Core.ps1 +++ b/Includes/Core.ps1 @@ -25,13 +25,21 @@ version date: 20191110 #> Function InitApplication { - $Variables | Add-Member -Force @{SourcesHash = @()} - $Variables | Add-Member -Force @{ProcessorCount = (Get-WmiObject -class win32_processor).NumberOfLogicalProcessors} - + $Variables.SourcesHash = @() + $Variables.ProcessorCount = (Get-WmiObject -class win32_processor).NumberOfLogicalProcessors + + $ServerPasswd = ConvertTo-SecureString $Config.Server_Password -AsPlainText -Force + $ServerCreds = New-Object System.Management.Automation.PSCredential ($Config.Server_User, $ServerPasswd) + $Variables.ServerCreds = $ServerCreds + $ServerClientPasswd = ConvertTo-SecureString $Config.Server_ClientPassword -AsPlainText -Force + $ServerClientCreds = New-Object System.Management.Automation.PSCredential ($Config.Server_ClientUser, $ServerClientPasswd) + $Variables.ServerClientCreds = $ServerClientCreds + if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + if (!(IsLoaded(".\Includes\Server.ps1"))) {. .\Includes\Server.ps1;RegisterLoaded(".\Includes\Server.ps1")} Set-Location (Split-Path $script:MyInvocation.MyCommand.Path) - $Variables | Add-Member -Force @{ScriptStartDate = (Get-Date)} + $Variables.ScriptStartDate = (Get-Date) # GitHub Supporting only TLSv1.2 on feb 22 2018 [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" Set-Location (Split-Path $script:MyInvocation.MyCommand.Path) @@ -45,11 +53,13 @@ Function InitApplication { if($Proxy -eq ""){$PSDefaultParameterValues.Remove("*:Proxy")} else{$PSDefaultParameterValues["*:Proxy"] = $Proxy} Update-Status("Initializing Variables...") - $Variables | Add-Member -Force @{DecayStart = Get-Date} - $Variables | Add-Member -Force @{DecayPeriod = 120} #seconds - $Variables | Add-Member -Force @{DecayBase = 1-0.1} #decimal percentage - $Variables | Add-Member -Force @{ActiveMinerPrograms = @()} - $Variables | Add-Member -Force @{Miners = @()} + $Variables.DecayStart = Get-Date + $Variables.DecayPeriod = 120 #seconds + $Variables.DecayBase = 1-0.1 #decimal percentage + # $Variables | Add-Member -Force @{ActiveMinerPrograms = @()} + $Variables["ActiveMinerPrograms"] = [System.Collections.ArrayList]::Synchronized(@()) + # $Variables | Add-Member -Force @{Miners = @()} + # $Variables["Miners"] = [System.Collections.ArrayList]::Synchronized(@()) #Start the log Start-Transcript -Path ".\Logs\miner-$((Get-Date).ToString('yyyyMMdd')).log" -Append -Force # Purge Logs more than 10 days @@ -59,22 +69,22 @@ Function InitApplication { #Update stats with missing data and set to today's date/time if(Test-Path "Stats"){Get-ChildItemContent "Stats" | ForEach {$Stat = Set-Stat $_.Name $_.Content.Week}} #Set donation parameters - $Variables | Add-Member -Force @{DonateRandom = [PSCustomObject]@{}} - $Variables | Add-Member -Force @{LastDonated = (Get-Date).AddDays(-1).AddHours(1)} + # $Variables | Add-Member -Force @{DonateRandom = [PSCustomObject]@{}} + $Variables.LastDonated = (Get-Date).AddDays(-1).AddHours(1) # If ($Config.Donate -lt 3) {$Config.Donate = (0,(3..8)) | Get-Random} - $Variables | Add-Member -Force @{WalletBackup = $Config.Wallet} - $Variables | Add-Member -Force @{UserNameBackup = $Config.UserName} - $Variables | Add-Member -Force @{WorkerNameBackup = $Config.WorkerName} - $Variables | Add-Member -Force @{EarningsPool = ""} - $Variables | Add-Member -Force @{BrainJobs = @()} - $Variables | Add-Member -Force @{EarningsTrackerJobs = @()} - $Variables | Add-Member -Force @{Earnings = @{}} - - - $Global:Variables | Add-Member -Force @{StartPaused = $False} - $Global:Variables | Add-Member -Force @{Started = $False} - $Global:Variables | Add-Member -Force @{Paused = $False} - $Global:Variables | Add-Member -Force @{RestartCycle = $False} + # $Variables | Add-Member -Force @{WalletBackup = $Config.Wallet} + # $Variables | Add-Member -Force @{UserNameBackup = $Config.UserName} + # $Variables | Add-Member -Force @{WorkerNameBackup = $Config.WorkerName} + # $Variables | Add-Member -Force @{EarningsPool = ""} + $Variables.BrainJobs = @() + $Variables.EarningsTrackerJobs = @() + $Variables.Earnings = [hashtable]::Synchronized(@{}) + + + $Global:Variables.StartPaused = $False + $Global:Variables.Started = $False + $Global:Variables.Paused = $False + $Global:Variables.RestartCycle = $False $Location = $Config.Location @@ -84,25 +94,50 @@ Function InitApplication { $Config.Type | sort | foreach { Update-Status("Finding available TCP Port for $($_)") $Port = Get-FreeTcpPort($StartPort) - $Variables | Add-Member -Force @{"$($_)MinerAPITCPPort" = $Port} + $Variables."$($_)MinerAPITCPPort" = $Port Update-Status("Miners API Port: $($Port)") $StartPort = $Port+1 } Sleep 2 + # Register Rig on Server + # need to decide on solution for IP address... Should be in config has could be external IP... + # If ($Config.Server_Client) { + # (Invoke-WebRequest "http://$($Config.Server_ClientIP):$($Config.Server_ClientPort)/RegisterRig/?name=$($Config.WorkerName)&IP=192.168.0.30&port=$($Config.ServerPort)" -Credential $Variables.ServerClientCreds) + # } + # Copy nvml.dll to proper location as latest drivers miss it - If ( (! (Test-Path "C:\Program Files\NVIDIA Corporation\NVSMI\nvml.dll")) -and (Test-Path "c:\Windows\System32\nvml.dll") ) { - Copy-Item "c:\Windows\System32\nvml.dll" "C:\Program Files\NVIDIA Corporation\NVSMI\nvml.dll" -Force -ErrorAction Ignore - } + # If ( (! (Test-Path "C:\Program Files\NVIDIA Corporation\NVSMI\nvml.dll")) -and (Test-Path "c:\Windows\System32\nvml.dll") ) { + # Copy-Item "c:\Windows\System32\nvml.dll" "C:\Program Files\NVIDIA Corporation\NVSMI\nvml.dll" -Force -ErrorAction Ignore + # } } Function Start-ChildJobs { + # Stop Server on code updates + If ($Config.Server_On -and $Variables.LocalServerRunning -and -not (IsLoaded(".\Includes\Server.ps1"))) { + $Variables.StatusText = "Stopping server for code update." + Invoke-WebRequest "http://localhost:$($Config.Server_Port)/StopServer" -Credential $Variables.ServerCreds + $Variables.LocalServerRunning = $False + } + + If ($Variables.LocalServerRunning) {$StartServerAttempt = 0} + + # Starts Server if necessary + If ($Config.Server_On -and -not $Variables.LocalServerRunning -and $StartServerAttempt -le 5 ) { + . .\Includes\Server.ps1;RegisterLoaded(".\Includes\Server.ps1") + $Variables.StatusText = "Starting Server" + $Variables.StopServer = $False + Start-Server + $StartServerAttempt++ + $Variables.LocalServerRunning = Try { ((Invoke-WebRequest "http://localhost:$($Config.Server_Port)/ping" -Credential $Variables.ServerCreds).content -eq "Server Alive")} Catch {$False} + } + # Starts Brains if necessary $Config.PoolName | foreach { if ($_ -notin $Variables.BrainJobs.PoolName){ $BrainPath = "$($Variables.MainPath)\BrainPlus\$($_)" $BrainName = (".\BrainPlus\"+$_+"\BrainPlus.ps1") if (Test-Path $BrainName){ - $Variables.StatusText = "Starting BrainPlus for $($_)..." + $Variables.StatusText = "Starting BrainPlus for $($_)" $BrainJob = Start-Job -FilePath $BrainName -ArgumentList @($BrainPath) $BrainJob | Add-Member -Force @{PoolName = $_} $Variables.BrainJobs += $BrainJob @@ -134,16 +169,38 @@ Function Start-ChildJobs { } Function NPMCycle { -$CycleTime = Measure-Command -Expression { +# $CycleTime = Measure-Command -Expression { +$CycleScriptBlock = { + [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1");"LoadedInclude" | out-host} - - $Variables | Add-Member -Force @{EndLoop = $False} + + (Get-Date).ToString() | out-host + + $Variables.EndLoop = $False $Variables.StatusText = "Starting Cycle" + If ($Config.Server_Client) { + $Variables.ServerRunning = Try{ ((Invoke-WebRequest "http://$($Config.Server_ClientIP):$($Config.Server_ClientPort)/ping" -Credential $Variables.ServerClientCreds -TimeoutSec 3).content -eq "Server Alive")} Catch {$False} + If ($Variables.ServerRunning){ + If ($Config.Server_ClientIP -and $Config.Server_ClientPort) { + Try { + Invoke-WebRequest "http://$($Config.Server_ClientIP):$($Config.Server_ClientPort)/RegisterRig/?Name=$($Config.WorkerName)&Port=$($Config.Server_Port)" -Credential $Variables.ServerClientCreds -TimeoutSec 5 + } Catch {"INFO: Failed to register on http://$($Config.Server_ClientIP):$($Config.Server_ClientPort)/RegisterRig/?Name=$($Config.WorkerName)&Port=$($Config.Server_Port)" | out-host} + } + Try { + $PeersFromServer = Invoke-WebRequest "http://$($Config.Server_ClientIP):$($Config.Server_ClientPort)/Peers.json" -Credential $Variables.ServerClientCreds | convertfrom-json + $PeersFromServer | ? {$_.Name -ne $Config.WorkerName} | ForEach { + $RegServer = $_.IP + $RegPort = $_.Port + Invoke-WebRequest "http://$($RegServer):$($RegPort)/RegisterRig/?Name=$($Config.WorkerName)&Port=$($Config.Server_Port)" -Credential $Variables.ServerClientCreds -TimeoutSec 5 + } + } Catch {"INFO: Failed to register on http://$($RegServer):$($RegPort)/RegisterRig/?Name=$($Config.WorkerName)&Port=$($Config.Server_Port)" | out-host} + } + } $DecayExponent = [int](((Get-Date)-$Variables.DecayStart).TotalSeconds/$Variables.DecayPeriod) # Ensure we get the hashrate for running miners prior looking for best miner - $Variables.ActiveMinerPrograms | ForEach { + $Variables["ActiveMinerPrograms"] | ForEach { if($_.Process -eq $null -or $_.Process.HasExited) { if($_.Status -eq "Running"){$_.Status = "Failed";$_.FailedCount++} @@ -171,14 +228,17 @@ $CycleTime = Measure-Command -Expression { } } } - + #Activate or deactivate donation - if((Get-Date).AddDays(-1).AddMinutes($Config.Donate) -ge $Variables.LastDonated -and $Variables.DonateRandom.wallet -eq $Null){ + # if((Get-Date).AddDays(-1).AddMinutes($Config.Donate) -ge $Variables.LastDonated -and $Variables.DonateRandom.wallet -eq $Null){ +# $Variables.LastDonated = (Get-Date).AddDays(-1).AddMinutes(-1) + if((Get-Date).AddDays(-1) -ge $Variables.LastDonated -and $Variables.DonateRandom.wallet -eq $Null){ # Get donation addresses randomly from agreed developers list # This will fairly distribute donations to Developers # Developers list and wallets is publicly available at: http://tiny.cc/r355qy $Variables.StatusText = "ENTERING DONATION" - $Variables | Add-Member -Force @{ DonationRunning = $True } + $Variables.DonationStart = $True + $Variables.DonationRunning = $False $Config.PartyWhenAvailable = $False try {$Donation = Invoke-WebRequest "http://tiny.cc/r355qy" -TimeoutSec 15 -UseBasicParsing -Headers @{"Cache-Control"="no-cache"} | ConvertFrom-Json} catch {$Donation = @([PSCustomObject]@{Name = "mrplus";Wallet = "134bw4oTorEJUUVFhokDQDfNqTs7rBMNYy";UserName = "mrplus"},[PSCustomObject]@{Name = "nemo";Wallet = "1QGADhdMRpp9Pk5u5zG1TrHKRrdK5R81TE";UserName = "nemo"})} if ($Donation -ne $null) { @@ -186,6 +246,8 @@ $CycleTime = Measure-Command -Expression { $Variables.DonateRandom = $Donation | Get-Random $DevPoolsConfig = [PSCustomObject]@{default = [PSCustomObject]@{Wallet = $Variables.DonateRandom.Wallet;UserName = $Variables.DonateRandom.UserName;WorkerName = "$($Variables.CurrentProduct)$($Variables.CurrentVersion.ToString().replace('.',''))";PricePenaltyFactor=1}} $Config | Add-Member -Force @{PoolsConfig = Merge-PoolsConfig -Main $Config.PoolsConfig -Secondary $DevPoolsConfig} + If ($Variables.DonateRandom.Donate) {$Config.Donate = $Variables.DonateRandom.Donate} + $Variables.DonationEndTime = (Get-Date).AddMinutes($Config.Donate) If ($Variables.DonateRandom.PoolsConfURL) { # Get Dev Pools Config try { @@ -204,9 +266,10 @@ $CycleTime = Measure-Command -Expression { } } } - if(((Get-Date).AddDays(-1) -ge $Variables.LastDonated -and $Variables.DonateRandom.Wallet -ne $Null) -or (! $Config.PoolsConfig)){ - If ($Variables.DonationRunning) {$Variables.StatusText = "EXITING DONATION"} - $Variables | Add-Member -Force @{ DonationRunning = $False } + if(((Get-Date) -ge $Variables.DonationEndTime -and $Variables.DonateRandom.Wallet -ne $Null) -or (! $Config.PoolsConfig)){ + If ($Variables.DonationStart -or $Variables.DonationRunning) {$Variables.StatusText = "EXITING DONATION"} + $Variables.DonationStart = $False + $Variables.DonationRunning = $False $ConfigLoad = Get-Content $Config.ConfigFile | ConvertFrom-json $ConfigLoad | % {$_.psobject.properties | sort Name | % {$Config | Add-Member -Force @{$_.Name = $_.Value}}} $Config | Add-Member -Force -MemberType ScriptProperty -Name "PoolsConfig" -Value { @@ -226,9 +289,10 @@ $CycleTime = Measure-Command -Expression { } $Variables.StatusText = "Loading BTC rate from 'api.coinbase.com'.." Try{ - $Rates = Invoke-RestMethod "https://api.coinbase.com/v2/exchange-rates?currency=BTC" -TimeoutSec 15 -UseBasicParsing | Select-Object -ExpandProperty data | Select-Object -ExpandProperty rates - $Config.Currency | Where-Object {$Rates.$_} | ForEach-Object {$Rates | Add-Member $_ ([Double]$Rates.$_) -Force} - $Variables | Add-Member -Force @{Rates = $Rates} + # $Rates = Invoke-RestMethod "https://api.coinbase.com/v2/exchange-rates?currency=BTC" -TimeoutSec 15 -UseBasicParsing | Select-Object -ExpandProperty data | Select-Object -ExpandProperty rates + $Rates = Invoke-ProxiedWebRequest "https://api.coinbase.com/v2/exchange-rates?currency=$($Config.Passwordcurrency)" -TimeoutSec 15 -UseBasicParsing | convertfrom-json | Select-Object -ExpandProperty data | Select-Object -ExpandProperty rates + $Config.Currency.Where( {$Rates.$_} ) | ForEach-Object {$Rates | Add-Member $_ ([Double]$Rates.$_) -Force} + $Variables.Rates = $Rates } catch {$Variables.StatusText = "Minor error - Failed to load BTC rate.."} #Load the Stats $Stats = [PSCustomObject]@{} @@ -238,31 +302,41 @@ $CycleTime = Measure-Command -Expression { $PoolFilter = @() $Config.PoolName | foreach {$PoolFilter+=($_+=".*")} Do { - $AllPools = if(Test-Path "Pools"){Get-ChildItemContent "Pools" -Include $PoolFilter | ForEach {$_.Content | Add-Member @{Name = $_.Name} -PassThru} | - Where {$_.SSL -EQ $Config.SSL -and ($Config.PoolName.Count -eq 0 -or ($_.Name -in $Config.PoolName)) -and (!$Config.Algorithm -or ((!($Config.Algorithm | ? {$_ -like "+*"}) -or $_.Algorithm -in ($Config.Algorithm | ? {$_ -like "+*"}).Replace("+","")) -and (!($Config.Algorithm | ? {$_ -like "-*"}) -or $_.Algorithm -notin ($Config.Algorithm | ? {$_ -like "-*"}).Replace("-",""))) )} + $Tries++ + $AllPools = if(Test-Path "Pools"){[System.Collections.ArrayList]::Synchronized(@(Get-SubScriptContent "Pools" -Include $PoolFilter)).Where({$_.Content -ne $Null}) | ForEach {$_.Content | Add-Member @{Name = $_.Name} -PassThru} | + Where { + $_.SSL -EQ $Config.SSL -and ($Config.PoolName.Count -eq 0 -or ($_.Name -in $Config.PoolName)) -and (!$Config.Algorithm -or ((!($Config.AlgoInclude) -or $_.Algorithm -in $Config.AlgoInclude) -and (!($Config.AlgoExclude) -or $_.Algorithm -notin $Config.AlgoExclude))) + } } if ($AllPools.Count -eq 0) { - $Variables.StatusText = "Waiting for pool data. retrying in 30 seconds.." - Sleep 30 - } - } While ($AllPools.Count -eq 0) + $Variables.StatusText = "Waiting for pool data. retrying in 30 seconds.." + Sleep 30 + } + } While ($AllPools.Count -eq 0 -and $Tries -le 3) + $Tries = 0 $Variables.StatusText = "Computing pool stats.." # Use location as preference and not the only one - $LocPools = @($AllPools | ?{$_.location -eq $Config.Location}) - $AllPools = $LocPools + ($AllPools | ? {$_.name -notin $LocPools.name}) - rv LocPools + $AllPoolsTemp = $AllPools + "AllPoolsCount = $($AllPools.Count)" | out-host + $AllPools = @($AllPools.Where({$_.location -eq $Config.Location})) + "AllPoolsCount = $($AllPools.Count) Loc only" | out-host + # $AllPools = $AllPools + @($AllPoolsTemp.Where({$_.name -notin $AllPools.name})) + $AllPools += (($AllPoolsTemp | sort name,algorithm,coin -Unique).Where({$_.name -notin ($AllPools.name | Sort -Unique)})) + "AllPoolsCount = $($AllPools.Count) After" | out-host + # rv LocPools # Filter Algo based on Per Pool Config $PoolsConf = $Config.PoolsConfig - $AllPools = $AllPools | Where {$_.Name -notin ($PoolsConf | Get-Member -MemberType NoteProperty | Select -ExpandProperty Name) -or ($_.Name -in ($PoolsConf | Get-Member -MemberType NoteProperty | Select -ExpandProperty Name) -and ((!($PoolsConf.($_.Name).Algorithm | ? {$_ -like "+*"}) -or ("+$($_.Algorithm)" -in $PoolsConf.($_.Name).Algorithm)) -and ("-$($_.Algorithm)" -notin $PoolsConf.($_.Name).Algorithm)))} + $AllPools = $AllPools.Where({$_.Name -notin ($PoolsConf | Get-Member -MemberType NoteProperty | Select -ExpandProperty Name) -or ($_.Name -in ($PoolsConf | Get-Member -MemberType NoteProperty | Select -ExpandProperty Name) -and ((!($PoolsConf.($_.Name).Algorithm | ? {$_ -like "+*"}) -or ("+$($_.Algorithm)" -in $PoolsConf.($_.Name).Algorithm)) -and ("-$($_.Algorithm)" -notin $PoolsConf.($_.Name).Algorithm)))}) - # if($AllPools.Count -eq 0){$Variables.StatusText = "Error contacting pool, retrying.."; $timerCycle.Interval = 15000 ; $timerCycle.Start() ; return} + # if($AllPools.Count -eq 0){$Variables.StatusText = "Error contacting pool, retrying.."; $timerCycle.Interval = 15000 ; $timerCycle.Start() ; return} $Pools = [PSCustomObject]@{} $Pools_Comparison = [PSCustomObject]@{} $AllPools.Algorithm | Sort -Unique | ForEach { + $Algo = $_ # $Pools | Add-Member $_ ($AllPools | Where Algorithm -EQ $_ | Sort Price -Descending | Select -First 1) # $Pools_Comparison | Add-Member $_ ($AllPools | Where Algorithm -EQ $_ | Sort StablePrice -Descending | Select -First 1) - $Pools | Add-Member $_ ($AllPools | Where Algorithm -EQ $_ | Sort Price -Descending) - $Pools_Comparison | Add-Member $_ ($AllPools | Where Algorithm -EQ $_ | Sort StablePrice -Descending) + $Pools | Add-Member $_ ($AllPools.Where({ $_.Algorithm -EQ $Algo }) | Sort Price -Descending) + $Pools_Comparison | Add-Member $_ ($AllPools.Where({ $_.Algorithm -EQ $Algo }) | Sort StablePrice -Descending) } # $AllPools.Algorithm | Select -Unique | ForEach {$Pools_Comparison | Add-Member $_ ($AllPools | Where Algorithm -EQ $_ | Sort StablePrice -Descending | Select -First 1)} #Load information about the Miners @@ -282,7 +356,7 @@ $CycleTime = Measure-Command -Expression { $NewMiner = &$_.path | select -first 1 $NewMiner | Add-Member -Force @{Name = (Get-Item $_.Path).BaseName} If ($NewMiner.Path -and (Test-Path (Split-Path $NewMiner.Path))) { - $Variables.ActiveMinerPrograms | Where { $_.Status -eq "Running" -and $_.Path -eq (Resolve-Path $NewMiner.Path)} | ForEach { + $Variables["ActiveMinerPrograms"].Where( { $_.Status -eq "Running" -and (Resolve-Path $_.Path).Path -eq (Resolve-Path $NewMiner.Path).Path} ) | ForEach { if($_.Process -eq $null) { $_.Status = "Failed" @@ -299,7 +373,8 @@ $CycleTime = Measure-Command -Expression { $_.Status = "Idle" } #Restore Bias for non-active miners - $Variables.Miners | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments | ForEach {$_.Profit_Bias = $_.Profit_Bias_Orig} + $Object = $_ + $Variables["Miners"].Where({ $_.Path -EQ $Object.Path -and $_.Arguments -EQ $Object.Arguments }) | ForEach {$_.Profit_Bias = $_.Profit_Bias_Orig} } # Force re-benchmark - Deactivated # Get-ChildItem -path ".\stats\" -filter "$($NewMiner.Name)_*.txt" | Remove-Item -Force -Recurse @@ -312,20 +387,20 @@ $CycleTime = Measure-Command -Expression { $Variables.StatusText = "Loading miners.." - $Variables | Add-Member -Force @{Miners = @()} + # $Variables | Add-Member -Force @{Miners = @()} $StartPort=4068 # Better load here than in miner file. Reduces disk reads. # $MinersConfig = If (Test-Path ".\Config\MinersConfig.json") { Get-content ".\Config\MinersConfig.json" | convertfrom-json } $Script:MinerCustomConfig = Get-Content ".\Config\MinerCustomConfig.json" | ConvertFrom-Json $Script:MinerCustomConfigCode = Get-Content ".\Includes\MinerCustomConfig.ps1" -raw - - $Variables.Miners = if (Test-Path "Miners") { - @( - Get-ChildItemContent "Miners" - if ($Config.IncludeOptionalMiners -and (Test-Path "OptionalMiners")) {Get-ChildItemContent "OptionalMiners"} - if (Test-Path "CustomMiners") { Get-ChildItemContent "CustomMiners"} - ) | ForEach { + $i = 0 + $Variables["Miners"] = if (Test-Path "Miners") { + [System.Collections.ArrayList]::Synchronized(@( + Get-SubScriptContent "Miners" + if ($Config.IncludeOptionalMiners -and (Test-Path "OptionalMiners")) {Get-SubScriptContent "OptionalMiners"} + if (Test-Path "CustomMiners") { Get-SubScriptContent "CustomMiners"} + )).Where({ $_.Content.Host -ne $Null -and $_.Content.Type -in $Config.Type }).ForEach({ $Miner = $_.Content | Add-Member @{Name = $_.Name} -PassThru # $Miner = $_ @@ -337,12 +412,22 @@ $CycleTime = Measure-Command -Expression { $Miner_Profits_Bias = [PSCustomObject]@{} $Miner_Types = $Miner.Type | Select -Unique $Miner_Indexes = $Miner.Index | Select -Unique + # $Miner.HashRates | Get-Member -MemberType NoteProperty | Select -ExpandProperty Name | ForEach { + # $LocPool = $Pools.$_ | Where {$_.Host -eq $Miner.Host -and $_.Coin -eq $Miner.Coin} + # $LocPoolsComp = $Pools_Comparison.$_ | Where {$_.Host -eq $Miner.Host -and $_.Coin -eq $Miner.Coin} + # $Miner_HashRates | Add-Member $_ ([Double]$Miner.HashRates.$_) + # $Miner_Pools | Add-Member $_ ([PSCustomObject]$LocPool) + # $Miner_Pools_Comparison | Add-Member $_ ([PSCustomObject]$LocPoolsComp | Where {$_.Host -eq $Miner.Host -and $_.Coin -eq $Miner.Coin}) + # $Miner_Profits | Add-Member $_ ([Double]$Miner.HashRates.$_*$LocPool.Price) + # $Miner_Profits_Comparison | Add-Member $_ ([Double]$Miner.HashRates.$_*$LocPoolsComp.Price) + # $Miner_Profits_Bias | Add-Member $_ ([Double]$Miner.HashRates.$_*$LocPool.Price*(1-($Config.MarginOfError*[Math]::Pow($Variables.DecayBase,$DecayExponent)))) + # } $Miner.HashRates | Get-Member -MemberType NoteProperty | Select -ExpandProperty Name | ForEach { - $LocPool = $Pools.$_ | ? {$_.Host -eq $Miner.Host -and $_.Coin -eq $Miner.Coin} - $LocPoolsComp = $Pools_Comparison.$_ | ? {$_.Host -eq $Miner.Host -and $_.Coin -eq $Miner.Coin} + $LocPool = $Pools.$_ | Where {$_.Host -in $Miner.Host -and $_.Coin -in $Miner.Coin} + $LocPoolsComp = $Pools_Comparison.$_ | Where {$_.Host -in $Miner.Host -and $_.Coin -in $Miner.Coin} $Miner_HashRates | Add-Member $_ ([Double]$Miner.HashRates.$_) $Miner_Pools | Add-Member $_ ([PSCustomObject]$LocPool) - $Miner_Pools_Comparison | Add-Member $_ ([PSCustomObject]$LocPoolsComp | ? {$_.Host -eq $Miner.Host -and $_.Coin -eq $Miner.Coin}) + $Miner_Pools_Comparison | Add-Member $_ ([PSCustomObject]$LocPoolsComp | Where {$_.Host -in $Miner.Host -and $_.Coin -in $Miner.Coin} ) $Miner_Profits | Add-Member $_ ([Double]$Miner.HashRates.$_*$LocPool.Price) $Miner_Profits_Comparison | Add-Member $_ ([Double]$Miner.HashRates.$_*$LocPoolsComp.Price) $Miner_Profits_Bias | Add-Member $_ ([Double]$Miner.HashRates.$_*$LocPool.Price*(1-($Config.MarginOfError*[Math]::Pow($Variables.DecayBase,$DecayExponent)))) @@ -362,8 +447,8 @@ $CycleTime = Measure-Command -Expression { $Miner_Profit_Bias = $null } } - if($Miner_Types -eq $null){$Miner_Types = $Variables.Miners.Type | Select -Unique} - if($Miner_Indexes -eq $null){$Miner_Indexes = $Variables.Miners.Index | Select -Unique} + if($Miner_Types -eq $null){$Miner_Types = $Variables["Miners"].Type | Select -Unique} + if($Miner_Indexes -eq $null){$Miner_Indexes = $Variables["Miners"].Index | Select -Unique} if($Miner_Types -eq $null){$Miner_Types = ""} if($Miner_Indexes -eq $null){$Miner_Indexes = 0} $Miner.HashRates = $Miner_HashRates @@ -380,24 +465,23 @@ $CycleTime = Measure-Command -Expression { # $Miner.Path = Convert-Path $Miner.Path $Miner_Devices = $Miner.Device | Select -Unique - if($Miner_Devices -eq $null){$Miner_Devices = ($Variables.Miners | Where {(Compare $Miner.Type $_.Type -IncludeEqual -ExcludeDifferent | Measure).Count -gt 0}).Device | Select -Unique} + if($Miner_Devices -eq $null){$Miner_Devices = ($Variables["Miners"].Where({(Compare $Miner.Type $_.Type -IncludeEqual -ExcludeDifferent | Measure).Count -gt 0})).Device | Select -Unique} if($Miner_Devices -eq $null){$Miner_Devices = $Miner.Type} $Miner | Add-Member Device $Miner_Devices -Force $Miner - } | - Where {$Config.Type.Count -eq 0 -or (Compare $Config.Type $_.Type -IncludeEqual -ExcludeDifferent | Measure).Count -gt 0} | - Where {!($Config.Algorithm | ? {$_.StartsWith("+")}) -or (Compare (($Config.Algorithm | ? {$_.StartsWith("+")}).Replace("+", "")) $_.HashRates.PSObject.Properties.Name -IncludeEqual -ExcludeDifferent | Measure).Count -gt 0} | - Where {$Config.MinerName.Count -eq 0 -or (Compare $Config.MinerName $_.Name -IncludeEqual -ExcludeDifferent | Measure).Count -gt 0} + }).Where( + {$Config.Type.Count -eq 0 -or (Compare $Config.Type $_.Type -IncludeEqual -ExcludeDifferent | Measure).Count -gt 0}).Where( + {$Config.MinerName.Count -eq 0 -or (Compare $Config.MinerName $_.Name -IncludeEqual -ExcludeDifferent | Measure).Count -gt 0}) } # 5.2.1 # Added sceurity to filter miners with no user name in case of malformed miner or pool file - $Variables.Miners = $Variables.Miners | ? {$_.User} + $Variables["Miners"] = $Variables["Miners"].Where({$_.User}) # 5.2.1 # Exclude non benchmarked during donation. - If ($Variables.DonationRunning) { - $Variables.Miners = $Variables.Miners | ? {$_.HashRates.Psobject.properties.value} + If ($Variables.DonationStart -or $Variables.DonationRunning) { + $Variables["Miners"] = $Variables["Miners"].Where({$_.HashRates.Psobject.properties.value}) } @@ -408,14 +492,14 @@ $CycleTime = Measure-Command -Expression { # ** Ban is not persistent across sessions ** If ($Config.MaxMinerFailure -gt 0){ $Config | Add-Member -Force @{ MaxMinerFailure = If ($Config.MaxMinerFailure) {$Config.MaxMinerFailure} else {3} } - $BannedMiners = $Variables.ActiveMinerPrograms | Where { $_.Status -eq "Failed" -and $_.FailedCount -ge $Config.MaxMinerFailure } + $BannedMiners = $Variables["ActiveMinerPrograms"].Where( { $_.Status -eq "Failed" -and $_.FailedCount -ge $Config.MaxMinerFailure } ) # $BannedMiners | foreach { $Variables.StatusText = "BANNED: $($_.Name) / $($_.Algorithms). Too many failures. Consider Algo exclusion in config." } $BannedMiners | foreach { "BANNED: $($_.Name) / $($_.Algorithms). Too many failures. Consider Algo exclusion in config." | Out-Host } - $Variables.Miners = $Variables.Miners | Where { $_.Path -notin $BannedMiners.Path -and $_.Arguments -notin $BannedMiners.Arguments } + $Variables["Miners"] = $Variables["Miners"].Where( { $_.Path -notin $BannedMiners.Path -and $_.Arguments -notin $BannedMiners.Arguments } ) } - $Variables.Miners | ? { (Test-Path $_.Path) -eq $false } | ForEach { + ($Variables["Miners"] | Sort Path,URI -Unique).Where({ (Test-Path $_.Path) -eq $false }) | ForEach { $Miner = $_ if((Test-Path $Miner.Path) -eq $false) { @@ -423,7 +507,7 @@ $CycleTime = Measure-Command -Expression { if((Split-Path $Miner.URI -Leaf) -eq (Split-Path $Miner.Path -Leaf)) { New-Item (Split-Path $Miner.Path) -ItemType "Directory" | Out-Null - Invoke-WebRequest $Miner.URI -TimeoutSec 15 -OutFile $_.Path -UseBasicParsing + Invoke-WebRequest $Miner.URI -UseBasicParsing -TimeoutSec 15 -OutFile $_.Path } elseif(([IO.FileInfo](Split-Path $_.URI -Leaf)).Extension -eq '') { @@ -452,196 +536,214 @@ $CycleTime = Measure-Command -Expression { } - $Variables.Miners = $Variables.Miners | ? { (Test-Path $_.Path) -eq $true } - - $Variables.StatusText = "Comparing miners and pools.." - if($Variables.Miners.Count -eq 0){$Variables.StatusText = "No Miners!"}#; sleep $Config.Interval; continue} - - # Remove miners when no estimation info from pools or 0BTC. Avoids mining when algo down at pool or benchmarking for ever - If (($Variables.Miners | ? {($_.Pools.PSObject.Properties.Value.Price -ne $null) -and ($_.Pools.PSObject.Properties.Value.Price -gt 0)}).Count -gt 0) {$Variables.Miners = $Variables.Miners | ? {($_.Pools.PSObject.Properties.Value.Price -ne $null) -and ($_.Pools.PSObject.Properties.Value.Price -gt 0)}} - #Don't penalize active miners. Miner could switch a little bit later and we will restore his bias in this case - $Variables.ActiveMinerPrograms | Where { $_.Status -eq "Running" } | ForEach {$Variables.Miners | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments | ForEach {$_.Profit_Bias = $_.Profit * (1 + $Config.ActiveMinerGainPct / 100)}} - #Get most profitable miner combination i.e. AMD+NVIDIA+CPU - $BestMiners = $Variables.Miners | Select Type,Index -Unique | ForEach {$Miner_GPU = $_; ($Variables.Miners | Where {(Compare $Miner_GPU.Type $_.Type | Measure).Count -eq 0 -and (Compare $Miner_GPU.Index $_.Index | Measure).Count -eq 0} | Sort -Descending {($_ | Where Profit -EQ $null | Measure).Count},{($_ | Measure Profit_Bias -Sum).Sum},{($_ | Where Profit -NE 0 | Measure).Count} | Select -First 1)} - $BestDeviceMiners = $Variables.Miners | Select Device -Unique | ForEach {$Miner_GPU = $_; ($Variables.Miners | Where {(Compare $Miner_GPU.Device $_.Device | Measure).Count -eq 0} | Sort -Descending {($_ | Where Profit -EQ $null | Measure).Count},{($_ | Measure Profit_Bias -Sum).Sum},{($_ | Where Profit -NE 0 | Measure).Count} | Select -First 1)} - $BestMiners_Comparison = $Variables.Miners | Select Type,Index -Unique | ForEach {$Miner_GPU = $_; ($Variables.Miners | Where {(Compare $Miner_GPU.Type $_.Type | Measure).Count -eq 0 -and (Compare $Miner_GPU.Index $_.Index | Measure).Count -eq 0} | Sort -Descending {($_ | Where Profit -EQ $null | Measure).Count},{($_ | Measure Profit_Comparison -Sum).Sum},{($_ | Where Profit -NE 0 | Measure).Count} | Select -First 1)} - $BestDeviceMiners_Comparison = $Variables.Miners | Select Device -Unique | ForEach {$Miner_GPU = $_; ($Variables.Miners | Where {(Compare $Miner_GPU.Device $_.Device | Measure).Count -eq 0} | Sort -Descending {($_ | Where Profit -EQ $null | Measure).Count},{($_ | Measure Profit_Comparison -Sum).Sum},{($_ | Where Profit -NE 0 | Measure).Count} | Select -First 1)} - $Miners_Type_Combos = @([PSCustomObject]@{Combination = @()}) + (Get-Combination ($Variables.Miners | Select Type -Unique) | Where{(Compare ($_.Combination | Select -ExpandProperty Type -Unique) ($_.Combination | Select -ExpandProperty Type) | Measure).Count -eq 0}) - $Miners_Index_Combos = @([PSCustomObject]@{Combination = @()}) + (Get-Combination ($Variables.Miners | Select Index -Unique) | Where{(Compare ($_.Combination | Select -ExpandProperty Index -Unique) ($_.Combination | Select -ExpandProperty Index) | Measure).Count -eq 0}) - $Miners_Device_Combos = (Get-Combination ($Variables.Miners | Select Device -Unique) | Where{(Compare ($_.Combination | Select -ExpandProperty Device -Unique) ($_.Combination | Select -ExpandProperty Device) | Measure).Count -eq 0}) - $BestMiners_Combos = $Miners_Type_Combos | ForEach {$Miner_Type_Combo = $_.Combination; $Miners_Index_Combos | ForEach {$Miner_Index_Combo = $_.Combination; [PSCustomObject]@{Combination = $Miner_Type_Combo | ForEach {$Miner_Type_Count = $_.Type.Count; [Regex]$Miner_Type_Regex = '^(' + (($_.Type | ForEach {[Regex]::Escape($_)}) -join '|') + ')$'; $Miner_Index_Combo | ForEach {$Miner_Index_Count = $_.Index.Count; [Regex]$Miner_Index_Regex = '^(' + (($_.Index | ForEach {[Regex]::Escape($_)}) -join '|') + ')$'; $BestMiners | Where {([Array]$_.Type -notmatch $Miner_Type_Regex).Count -eq 0 -and ([Array]$_.Index -notmatch $Miner_Index_Regex).Count -eq 0 -and ([Array]$_.Type -match $Miner_Type_Regex).Count -eq $Miner_Type_Count -and ([Array]$_.Index -match $Miner_Index_Regex).Count -eq $Miner_Index_Count}}}}}} - $BestMiners_Combos += $Miners_Device_Combos | ForEach {$Miner_Device_Combo = $_.Combination; [PSCustomObject]@{Combination = $Miner_Device_Combo | ForEach {$Miner_Device_Count = $_.Device.Count; [Regex]$Miner_Device_Regex = '^(' + (($_.Device | ForEach {[Regex]::Escape($_)}) -join '|') + ')$'; $BestDeviceMiners | Where {([Array]$_.Device -notmatch $Miner_Device_Regex).Count -eq 0 -and ([Array]$_.Device -match $Miner_Device_Regex).Count -eq $Miner_Device_Count}}}} - $BestMiners_Combos_Comparison = $Miners_Type_Combos | ForEach {$Miner_Type_Combo = $_.Combination; $Miners_Index_Combos | ForEach {$Miner_Index_Combo = $_.Combination; [PSCustomObject]@{Combination = $Miner_Type_Combo | ForEach {$Miner_Type_Count = $_.Type.Count; [Regex]$Miner_Type_Regex = '^(' + (($_.Type | ForEach {[Regex]::Escape($_)}) -join '|') + ')$'; $Miner_Index_Combo | ForEach {$Miner_Index_Count = $_.Index.Count; [Regex]$Miner_Index_Regex = '^(' + (($_.Index | ForEach {[Regex]::Escape($_)}) -join '|') + ')$'; $BestMiners_Comparison | Where {([Array]$_.Type -notmatch $Miner_Type_Regex).Count -eq 0 -and ([Array]$_.Index -notmatch $Miner_Index_Regex).Count -eq 0 -and ([Array]$_.Type -match $Miner_Type_Regex).Count -eq $Miner_Type_Count -and ([Array]$_.Index -match $Miner_Index_Regex).Count -eq $Miner_Index_Count}}}}}} - $BestMiners_Combos_Comparison += $Miners_Device_Combos | ForEach {$Miner_Device_Combo = $_.Combination; [PSCustomObject]@{Combination = $Miner_Device_Combo | ForEach {$Miner_Device_Count = $_.Device.Count; [Regex]$Miner_Device_Regex = '^(' + (($_.Device | ForEach {[Regex]::Escape($_)}) -join '|') + ')$'; $BestDeviceMiners_Comparison | Where {([Array]$_.Device -notmatch $Miner_Device_Regex).Count -eq 0 -and ([Array]$_.Device -match $Miner_Device_Regex).Count -eq $Miner_Device_Count}}}} - $BestMiners_Combo = $BestMiners_Combos | Sort -Descending {($_.Combination | Where Profit -EQ $null | Measure).Count},{($_.Combination | Measure Profit_Bias -Sum).Sum},{($_.Combination | Where Profit -NE 0 | Measure).Count} | Select -First 1 | Select -ExpandProperty Combination - $BestMiners_Combo_Comparison = $BestMiners_Combos_Comparison | Sort -Descending {($_.Combination | Where Profit -EQ $null | Measure).Count},{($_.Combination | Measure Profit_Comparison -Sum).Sum},{($_.Combination | Where Profit -NE 0 | Measure).Count} | Select -First 1 | Select -ExpandProperty Combination - # No CPU mining if GPU miner prevents it - If ($BestMiners_Combo.PreventCPUMining -contains $true) { - $BestMiners_Combo = $BestMiners_Combo | ? {$_.type -ne "CPU"} - $Variables.StatusText = "Miner prevents CPU mining" - } - - If ($Variables.DonationRunning) { - $BestMiners_Combo | % { - $_.Arguments = $_.Arguments -replace "$($Config.PoolsConfig.Default.WorkerName)","$($Config.PoolsConfig.Default.WorkerName)_$($_.Type)" + $Variables["Miners"] = $Variables["Miners"].Where({ (Test-Path $_.Path) -eq $true }) + + # If (! $Variables.DonationRunning) { + $Variables.StatusText = "Comparing miners and pools.." + if($Variables["Miners"].Count -eq 0){$Variables.StatusText = "No Miners!"}#; sleep $Config.Interval; continue} + + # Remove miners when no estimation info from pools or 0BTC. Avoids mining when algo down at pool or benchmarking for ever + If (($Variables["Miners"] | ? {($_.Pools.PSObject.Properties.Value.Price -ne $null) -and ($_.Pools.PSObject.Properties.Value.Price -gt 0)}).Count -gt 0) {$Variables["Miners"] = $Variables["Miners"] | ? {($_.Pools.PSObject.Properties.Value.Price -ne $null) -and ($_.Pools.PSObject.Properties.Value.Price -gt 0)}} + #Don't penalize active miners. Miner could switch a little bit later and we will restore his bias in this case + $Variables["ActiveMinerPrograms"] | Where { $_.Status -eq "Running" } | ForEach {$Variables["Miners"] | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments | ForEach {$_.Profit_Bias = $_.Profit * (1 + $Config.ActiveMinerGainPct / 100)}} + #Get most profitable miner combination i.e. AMD+NVIDIA+CPU + $BestMiners = $Variables["Miners"] | Select Type,Index -Unique | ForEach {$Miner_GPU = $_; ($Variables["Miners"] | Where {(Compare $Miner_GPU.Type $_.Type | Measure).Count -eq 0 -and (Compare $Miner_GPU.Index $_.Index | Measure).Count -eq 0} | Sort -Descending {($_ | Where Profit -EQ $null | Measure).Count},{($_ | Measure Profit_Bias -Sum).Sum},{($_ | Where Profit -NE 0 | Measure).Count} | Select -First 1)} + $BestDeviceMiners = $Variables["Miners"] | Select Device -Unique | ForEach {$Miner_GPU = $_; ($Variables["Miners"] | Where {(Compare $Miner_GPU.Device $_.Device | Measure).Count -eq 0} | Sort -Descending {($_ | Where Profit -EQ $null | Measure).Count},{($_ | Measure Profit_Bias -Sum).Sum},{($_ | Where Profit -NE 0 | Measure).Count} | Select -First 1)} + $BestMiners_Comparison = $Variables["Miners"] | Select Type,Index -Unique | ForEach {$Miner_GPU = $_; ($Variables["Miners"] | Where {(Compare $Miner_GPU.Type $_.Type | Measure).Count -eq 0 -and (Compare $Miner_GPU.Index $_.Index | Measure).Count -eq 0} | Sort -Descending {($_ | Where Profit -EQ $null | Measure).Count},{($_ | Measure Profit_Comparison -Sum).Sum},{($_ | Where Profit -NE 0 | Measure).Count} | Select -First 1)} + $BestDeviceMiners_Comparison = $Variables["Miners"] | Select Device -Unique | ForEach {$Miner_GPU = $_; ($Variables["Miners"] | Where {(Compare $Miner_GPU.Device $_.Device | Measure).Count -eq 0} | Sort -Descending {($_ | Where Profit -EQ $null | Measure).Count},{($_ | Measure Profit_Comparison -Sum).Sum},{($_ | Where Profit -NE 0 | Measure).Count} | Select -First 1)} + $Miners_Type_Combos = @([PSCustomObject]@{Combination = @()}) + (Get-Combination ($Variables["Miners"] | Select Type -Unique) | Where{(Compare ($_.Combination | Select -ExpandProperty Type -Unique) ($_.Combination | Select -ExpandProperty Type) | Measure).Count -eq 0}) + $Miners_Index_Combos = @([PSCustomObject]@{Combination = @()}) + (Get-Combination ($Variables["Miners"] | Select Index -Unique) | Where{(Compare ($_.Combination | Select -ExpandProperty Index -Unique) ($_.Combination | Select -ExpandProperty Index) | Measure).Count -eq 0}) + $Miners_Device_Combos = (Get-Combination ($Variables["Miners"] | Select Device -Unique) | Where{(Compare ($_.Combination | Select -ExpandProperty Device -Unique) ($_.Combination | Select -ExpandProperty Device) | Measure).Count -eq 0}) + $BestMiners_Combos = $Miners_Type_Combos | ForEach {$Miner_Type_Combo = $_.Combination; $Miners_Index_Combos | ForEach {$Miner_Index_Combo = $_.Combination; [PSCustomObject]@{Combination = $Miner_Type_Combo | ForEach {$Miner_Type_Count = $_.Type.Count; [Regex]$Miner_Type_Regex = '^(' + (($_.Type | ForEach {[Regex]::Escape($_)}) -join '|') + ')$'; $Miner_Index_Combo | ForEach {$Miner_Index_Count = $_.Index.Count; [Regex]$Miner_Index_Regex = '^(' + (($_.Index | ForEach {[Regex]::Escape($_)}) -join '|') + ')$'; $BestMiners | Where {([Array]$_.Type -notmatch $Miner_Type_Regex).Count -eq 0 -and ([Array]$_.Index -notmatch $Miner_Index_Regex).Count -eq 0 -and ([Array]$_.Type -match $Miner_Type_Regex).Count -eq $Miner_Type_Count -and ([Array]$_.Index -match $Miner_Index_Regex).Count -eq $Miner_Index_Count}}}}}} + $BestMiners_Combos += $Miners_Device_Combos | ForEach {$Miner_Device_Combo = $_.Combination; [PSCustomObject]@{Combination = $Miner_Device_Combo | ForEach {$Miner_Device_Count = $_.Device.Count; [Regex]$Miner_Device_Regex = '^(' + (($_.Device | ForEach {[Regex]::Escape($_)}) -join '|') + ')$'; $BestDeviceMiners | Where {([Array]$_.Device -notmatch $Miner_Device_Regex).Count -eq 0 -and ([Array]$_.Device -match $Miner_Device_Regex).Count -eq $Miner_Device_Count}}}} + $BestMiners_Combos_Comparison = $Miners_Type_Combos | ForEach {$Miner_Type_Combo = $_.Combination; $Miners_Index_Combos | ForEach {$Miner_Index_Combo = $_.Combination; [PSCustomObject]@{Combination = $Miner_Type_Combo | ForEach {$Miner_Type_Count = $_.Type.Count; [Regex]$Miner_Type_Regex = '^(' + (($_.Type | ForEach {[Regex]::Escape($_)}) -join '|') + ')$'; $Miner_Index_Combo | ForEach {$Miner_Index_Count = $_.Index.Count; [Regex]$Miner_Index_Regex = '^(' + (($_.Index | ForEach {[Regex]::Escape($_)}) -join '|') + ')$'; $BestMiners_Comparison | Where {([Array]$_.Type -notmatch $Miner_Type_Regex).Count -eq 0 -and ([Array]$_.Index -notmatch $Miner_Index_Regex).Count -eq 0 -and ([Array]$_.Type -match $Miner_Type_Regex).Count -eq $Miner_Type_Count -and ([Array]$_.Index -match $Miner_Index_Regex).Count -eq $Miner_Index_Count}}}}}} + $BestMiners_Combos_Comparison += $Miners_Device_Combos | ForEach {$Miner_Device_Combo = $_.Combination; [PSCustomObject]@{Combination = $Miner_Device_Combo | ForEach {$Miner_Device_Count = $_.Device.Count; [Regex]$Miner_Device_Regex = '^(' + (($_.Device | ForEach {[Regex]::Escape($_)}) -join '|') + ')$'; $BestDeviceMiners_Comparison | Where {([Array]$_.Device -notmatch $Miner_Device_Regex).Count -eq 0 -and ([Array]$_.Device -match $Miner_Device_Regex).Count -eq $Miner_Device_Count}}}} + $BestMiners_Combo = $BestMiners_Combos | Sort -Descending {($_.Combination | Where Profit -EQ $null | Measure).Count},{($_.Combination | Measure Profit_Bias -Sum).Sum},{($_.Combination | Where Profit -NE 0 | Measure).Count} | Select -First 1 | Select -ExpandProperty Combination + $BestMiners_Combo_Comparison = $BestMiners_Combos_Comparison | Sort -Descending {($_.Combination | Where Profit -EQ $null | Measure).Count},{($_.Combination | Measure Profit_Comparison -Sum).Sum},{($_.Combination | Where Profit -NE 0 | Measure).Count} | Select -First 1 | Select -ExpandProperty Combination + # No CPU mining if GPU miner prevents it + If ($BestMiners_Combo.PreventCPUMining -contains $true) { + $BestMiners_Combo = $BestMiners_Combo | ? {$_.type -ne "CPU"} + $Variables.StatusText = "Miner prevents CPU mining" + } + # } + + # Prevent switching during donation + If ($Variables.DonationStart -or $Variables.DonationRunning) { + If ($Variables.DonationRunning) {$BestMiners_Combo = $Variables.DonationBestMiners_Combo} + If ($Variables.DonationStart) { + $BestMiners_Combo | % { + $_.Arguments = $_.Arguments -replace "$($Config.PoolsConfig.Default.WorkerName)","$($Config.PoolsConfig.Default.WorkerName)_$($_.Type)" + } + $Variables.DonationBestMiners_Combo = $BestMiners_Combo } } $Variables.StatusText = "Assigning miners.." #Add the most profitable miners to the active list - $BestMiners_Combo | ForEach { - if(($Variables.ActiveMinerPrograms | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments).Count -eq 0) - { - $Variables.ActiveMinerPrograms += [PSCustomObject]@{ - Type = $_.Type - Name = $_.Name - Path = $_.Path - Arguments = $_.Arguments - Wrap = $_.Wrap - Process = $null - API = $_.API - Port = $_.Port - Algorithms = $_.HashRates.PSObject.Properties.Name - New = $false - Active = [TimeSpan]0 - TotalActive = [TimeSpan]0 - Activated = 0 - FailedCount = 0 - Status = "Idle" - HashRate = 0 - Benchmarked = 0 - Hashrate_Gathered = ($_.HashRates.PSObject.Properties.Value -ne $null) - User = $_.User - Host = $_.Host - Coin = $_.Coin + # Prevent switching during donation + # If (! $Variables.DonationRunning -or ($Variables["ActiveMinerPrograms"] | ? {$_.Status -ne "Idle" -and ($_.Process -eq $null -or $_.Process.HasExited -ne $false})).Count -gt 0) { + # If (! $Variables.DonationRunning) { + $BestMiners_Combo | ForEach { + if(($Variables["ActiveMinerPrograms"] | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments).Count -eq 0) + { + $Variables["ActiveMinerPrograms"] += [PSCustomObject]@{ + Type = $_.Type + Name = $_.Name + Path = $_.Path + Arguments = $_.Arguments + Wrap = $_.Wrap + Process = $null + API = $_.API + Port = $_.Port + Algorithms = $_.HashRates.PSObject.Properties.Name + New = $false + Active = [TimeSpan]0 + TotalActive = [TimeSpan]0 + Activated = 0 + FailedCount = 0 + Status = "Idle" + HashRate = 0 + Benchmarked = 0 + Hashrate_Gathered = ($_.HashRates.PSObject.Properties.Value -ne $null) + User = $_.User + Host = $_.Host + Coin = $_.Coin + Pools = $_.Pools + } } } - } - #Stop or start miners in the active list depending on if they are the most profitable - # We have to stop processes first or the port would be busy - $Variables.ActiveMinerPrograms | ForEach { - [Array]$filtered = ($BestMiners_Combo | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments) - if($filtered.Count -eq 0) - { - if($_.Process -eq $null) + #Stop or start miners in the active list depending on if they are the most profitable + # We have to stop processes first or the port would be busy + $Variables["ActiveMinerPrograms"] | ForEach { + [Array]$filtered = ($BestMiners_Combo | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments) + if($filtered.Count -eq 0) { - $_.Status = "Failed" - $_.FailedCount++ - } - elseif ($_.Process.HasExited -eq $false) { - $_.Process.CloseMainWindow() | Out-Null - Sleep 1 - # simply "Kill with power" - Stop-Process $_.Process -Force | Out-Null - # Try to kill any process with the same path, in case it is still running but the process handle is incorrect - $KillPath = $_.Path - Get-Process | Where-Object {$_.Path -eq $KillPath} | Stop-Process -Force - Write-Host -ForegroundColor Yellow "closing miner" - Sleep 1 - $_.Status = "Idle" + if($_.Process -eq $null) + { + $_.Status = "Failed" + $_.FailedCount++ + } + elseif ($_.Process.HasExited -eq $false) { + $_.Process.CloseMainWindow() | Out-Null + Sleep 1 + # simply "Kill with power" + Stop-Process $_.Process -Force | Out-Null + # Try to kill any process with the same path, in case it is still running but the process handle is incorrect + $KillPath = $_.Path + Get-Process | Where-Object {$_.Path -eq $KillPath} | Stop-Process -Force + Write-Host -ForegroundColor Yellow "closing miner" + Sleep 1 + $_.Status = "Idle" + } + #Restore Bias for non-active miners + $Variables["Miners"] | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments | ForEach {$_.Profit_Bias = $_.Profit_Bias_Orig} } - #Restore Bias for non-active miners - $Variables.Miners | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments | ForEach {$_.Profit_Bias = $_.Profit_Bias_Orig} } - } - $newMiner = $false - $CurrentMinerHashrate_Gathered =$false - $newMiner = $false - $CurrentMinerHashrate_Gathered =$false - $Variables.ActiveMinerPrograms | ForEach { - [Array]$filtered = ($BestMiners_Combo | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments) - if($filtered.Count -gt 0) - { - if($_.Process -eq $null -or $_.Process.HasExited -ne $false) + $newMiner = $false + $CurrentMinerHashrate_Gathered =$false + $newMiner = $false + $CurrentMinerHashrate_Gathered =$false + $Variables["ActiveMinerPrograms"] | ForEach { + [Array]$filtered = ($BestMiners_Combo | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments) + if($filtered.Count -gt 0) { - # Migrate previous version of .\log\switching.log (Add Coin) - # Move to app init - If (!(Get-Content .\Logs\switching.log -First 1).Contains("coin")) { - $tmp = @() - Import-Csv .\Logs\switching.log | % {$tmp += [PsCustomObject][Ordered]@{date=$_.date;Type=$_.Type;algo=$_.Algo;coin=$_.Coin;wallet=$_.wallet;username=$_.UserName;Host=$_.host}} - $tmp | export-csv .\Logs\switching.log -NoTypeInformation -Force - rv tmp - } - # Log switching information to .\log\switching.log - [pscustomobject]@{date=(get-date);Type=$_.Type;algo=$_.Algorithms;coin=$_.Coin;wallet=$_.User;username=$Config.UserName;Host=$_.host} | export-csv .\Logs\switching.log -Append -NoTypeInformation -Force + if($_.Process -eq $null -or $_.Process.HasExited -ne $false) + { + # Migrate previous version of .\log\switching.log (Add Coin) + # Move to app init + If (Test-Path ".\Logs\switching.log") { + If (!(Get-Content ".\Logs\switching.log" -First 1).Contains("coin")) { + $tmp = @() + Import-Csv .\Logs\switching.log | % {$tmp += [PsCustomObject][Ordered]@{date=$_.date;Type=$_.Type;algo=$_.Algo;coin=$_.Coin;wallet=$_.wallet;username=$_.UserName;Host=$_.host}} + $tmp | export-csv .\Logs\switching.log -NoTypeInformation -Force + rv tmp + } + } + # Log switching information to .\log\switching.log + [pscustomobject]@{date=(get-date);Type=$_.Type;algo=$_.Algorithms -join ',';coin=$_.Coin -join ',';wallet=$_.User -join ',';username=$Config.UserName -join ',';Host=$_.host -join ','} | export-csv .\Logs\switching.log -Append -NoTypeInformation -Force + If ($Variables.DonationStart) { + $Variables.DonationStart = $False + $Variables.DonationRunning = $True + } - # Launch prerun if exists - If ($_.Type -eq "AMD" -and (Test-Path ".\Prerun\AMDPrerun.bat")) { - Start-Process ".\Prerun\AMDPrerun.bat" -WorkingDirectory ".\Prerun" -WindowStyle hidden - } - If ($_.Type -eq "NVIDIA" -and (Test-Path ".\Prerun\NVIDIAPrerun.bat")) { - Start-Process ".\Prerun\NVIDIAPrerun.bat" -WorkingDirectory ".\Prerun" -WindowStyle hidden - } - If ($_.Type -eq "CPU" -and (Test-Path ".\Prerun\CPUPrerun.bat")) { - Start-Process ".\Prerun\CPUPrerun.bat" -WorkingDirectory ".\Prerun" -WindowStyle hidden - } - If ($_.Type -ne "CPU") { - $PrerunName = ".\Prerun\"+$_.Algorithms+".bat" - $DefaultPrerunName = ".\Prerun\default.bat" - If (Test-Path $PrerunName) { - $Variables.StatusText = "Launching Prerun: $PrerunName" - Start-Process $PrerunName -WorkingDirectory ".\Prerun" -WindowStyle hidden - Sleep 2 - } else { - If (Test-Path $DefaultPrerunName) { - $Variables.StatusText = "Launching Prerun: $DefaultPrerunName" - Start-Process $DefaultPrerunName -WorkingDirectory ".\Prerun" -WindowStyle hidden + # Launch prerun if exists + If ($_.Type -eq "AMD" -and (Test-Path ".\Prerun\AMDPrerun.bat")) { + Start-Process ".\Prerun\AMDPrerun.bat" -WorkingDirectory ".\Prerun" -WindowStyle hidden + } + If ($_.Type -eq "NVIDIA" -and (Test-Path ".\Prerun\NVIDIAPrerun.bat")) { + Start-Process ".\Prerun\NVIDIAPrerun.bat" -WorkingDirectory ".\Prerun" -WindowStyle hidden + } + If ($_.Type -eq "CPU" -and (Test-Path ".\Prerun\CPUPrerun.bat")) { + Start-Process ".\Prerun\CPUPrerun.bat" -WorkingDirectory ".\Prerun" -WindowStyle hidden + } + If ($_.Type -ne "CPU") { + $PrerunName = ".\Prerun\"+$_.Algorithms+".bat" + $DefaultPrerunName = ".\Prerun\default.bat" + If (Test-Path $PrerunName) { + $Variables.StatusText = "Launching Prerun: $PrerunName" + Start-Process $PrerunName -WorkingDirectory ".\Prerun" -WindowStyle hidden Sleep 2 - } + } else { + If (Test-Path $DefaultPrerunName) { + $Variables.StatusText = "Launching Prerun: $DefaultPrerunName" + Start-Process $DefaultPrerunName -WorkingDirectory ".\Prerun" -WindowStyle hidden + Sleep 2 + } + } } - } - Sleep $Config.Delay #Wait to prevent BSOD - $Variables.StatusText = "Starting miner" - $Variables.DecayStart = Get-Date - $_.New = $true - $_.Activated++ - # if($_.Process -ne $null){$_.TotalActive += $_.Process.ExitTime-$_.Process.StartTime} - if($_.Process -ne $null){$_.Active = [TimeSpan]0} - - if($_.Wrap){$_.Process = Start-Process -FilePath "PowerShell" -ArgumentList "-executionpolicy bypass -command . '$(Convert-Path ".\Includes\Wrapper.ps1")' -ControllerProcessID $PID -Id '$($_.Port)' -FilePath '$($_.Path)' -ArgumentList '$($_.Arguments)' -WorkingDirectory '$(Split-Path $_.Path)'" -PassThru} - else{$_.Process = Start-SubProcess -FilePath $_.Path -ArgumentList $_.Arguments -WorkingDirectory (Split-Path $_.Path)} - if($_.Process -eq $null){$_.Status = "Failed";$_.FailedCount++} - else { - $_.Status = "Running" - $newMiner = $true - #Newely started miner should looks better than other in the first run too - $Variables.Miners | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments | ForEach {$_.Profit_Bias = $_.Profit * (1 + $Config.ActiveMinerGainPct / 100)} + Sleep $Config.Delay #Wait to prevent BSOD + $Variables.StatusText = "Starting miner" + $Variables.DecayStart = Get-Date + $_.New = $true + $_.Activated++ + # if($_.Process -ne $null){$_.TotalActive += $_.Process.ExitTime-$_.Process.StartTime} + if($_.Process -ne $null){$_.Active = [TimeSpan]0} + + if($_.Wrap){$_.Process = Start-Process -FilePath "PowerShell" -ArgumentList "-executionpolicy bypass -command . '$(Convert-Path ".\Includes\Wrapper.ps1")' -ControllerProcessID $PID -Id '$($_.Port)' -FilePath '$($_.Path)' -ArgumentList '$($_.Arguments)' -WorkingDirectory '$(Split-Path $_.Path)'" -PassThru} + else{$_.Process = Start-SubProcess -FilePath $_.Path -ArgumentList $_.Arguments -WorkingDirectory (Split-Path $_.Path)} + if($_.Process -eq $null){$_.Status = "Failed";$_.FailedCount++} + else { + $_.Status = "Running" + $newMiner = $true + #Newely started miner should looks better than other in the first run too + $Variables["Miners"] | Where Path -EQ $_.Path | Where Arguments -EQ $_.Arguments | ForEach {$_.Profit_Bias = $_.Profit * (1 + $Config.ActiveMinerGainPct / 100)} + } + } else { + $now = Get-Date + $_.TotalActive = $_.TotalActive + ( $Now - $_.Process.StartTime ) - $_.Active + $_.Active = $Now - $_.Process.StartTime } - } else { - $now = Get-Date - $_.TotalActive = $_.TotalActive + ( $Now - $_.Process.StartTime ) - $_.Active - $_.Active = $Now - $_.Process.StartTime + $CurrentMinerHashrate_Gathered = $_.Hashrate_Gathered } - $CurrentMinerHashrate_Gathered = $_.Hashrate_Gathered } - } + # } #Do nothing for a few seconds as to not overload the APIs if ($newMiner -eq $true) { - if ($Config.Interval -ge $Config.FirstInterval -and $Config.Interval -ge $Config.StatsInterval) { $Variables.TimeToSleep = $Config.Interval } - else { + # if ($Config.Interval -ge $Config.FirstInterval -and $Config.Interval -ge $Config.StatsInterval) { $Variables.TimeToSleep = $Config.Interval } + # else { if ($CurrentMinerHashrate_Gathered -eq $true) { $Variables.TimeToSleep = $Config.FirstInterval } else { $Variables.TimeToSleep = $Config.StatsInterval } - } + # } } else { $Variables.TimeToSleep = $Config.Interval } "--------------------------------------------------------------------------------" | out-host #Do nothing for a few seconds as to not overload the APIs if ($newMiner -eq $true) { - if ($Config.Interval -ge $Config.FirstInterval -and $Config.Interval -ge $Config.StatsInterval) { $Variables.TimeToSleep = $Config.Interval } - else { + # if ($Config.Interval -ge $Config.FirstInterval -and $Config.Interval -ge $Config.StatsInterval) { $Variables.TimeToSleep = $Config.Interval } + # else { if ($CurrentMinerHashrate_Gathered -eq $true) { $Variables.TimeToSleep = $Config.FirstInterval } else { $Variables.TimeToSleep = $Config.StatsInterval } - } + # } } else { $Variables.TimeToSleep = $Config.Interval } # Prevent switching during donation - If ( $Variables.DonationRunning ) { If ($Config.Interval -ge ($Config.Donate * 60)) {$Variables.TimeToSleep = $Config.Interval} else {$Variables.TimeToSleep = $Config.Donate * 60 }} + # If ( $Variables.DonationRunning ) { If ($Config.Interval -ge ($Config.Donate * 60)) {$Variables.TimeToSleep = $Config.Interval} else {$Variables.TimeToSleep = $Config.Donate * 60 }} #Save current hash rates - $Variables.ActiveMinerPrograms | ForEach { + $Variables["ActiveMinerPrograms"] | ForEach { if($_.Process -eq $null -or $_.Process.HasExited) { if($_.Status -eq "Running"){$_.Status = "Failed";$_.FailedCount++} @@ -684,15 +786,15 @@ $CycleTime = Measure-Command -Expression { # } <# - For some reason (need to investigate) $Variables.ActiveMinerPrograms.psobject.TypeNames + For some reason (need to investigate) $Variables["ActiveMinerPrograms"].psobject.TypeNames Inflates adding several lines at each loop and causing a memory leak after log runtime Code below copies the object which results in a new version which avoid the problem. Will need rework. #> - $Variables.ActiveMinerPrograms | Where {$_.Status -ne "Running"} | foreach {$_.process = $_.process | select HasExited,StartTime,ExitTime} - $ActiveMinerProgramsCOPY = @() - $Variables.ActiveMinerPrograms | %{$ActiveMinerCOPY = [PSCustomObject]@{}; $_.psobject.properties | sort Name | %{$ActiveMinerCOPY | Add-Member -Force @{$_.Name = $_.Value}};$ActiveMinerProgramsCOPY += $ActiveMinerCOPY} - $Variables.ActiveMinerPrograms = $ActiveMinerProgramsCOPY + $Variables["ActiveMinerPrograms"] | Where {$_.Status -ne "Running"} | foreach {$_.process = $_.process | select HasExited,StartTime,ExitTime} + $ActiveMinerProgramsCOPY = [System.Collections.ArrayList]::Synchronized(@()) + $Variables["ActiveMinerPrograms"] | %{$ActiveMinerCOPY = [PSCustomObject]@{}; $_.psobject.properties | sort Name | %{$ActiveMinerCOPY | Add-Member -Force @{$_.Name = $_.Value}};$ActiveMinerProgramsCOPY += $ActiveMinerCOPY} + $Variables["ActiveMinerPrograms"] = $ActiveMinerProgramsCOPY rv ActiveMinerProgramsCOPY rv ActiveMinerCOPY @@ -714,21 +816,25 @@ $CycleTime = Measure-Command -Expression { # Mostly used for debug. Will execute code found in .\EndLoopCode.ps1 if exists. if (Test-Path ".\EndLoopCode.ps1"){Invoke-Expression (Get-Content ".\EndLoopCode.ps1" -Raw)} } + + $CycleTime = Measure-Command -Expression $CycleScriptBlock.Ast.GetScriptBlock() + # $Variables.StatusText = "Cycle Time (seconds): $($CycleTime.TotalSeconds)" - "Cycle Time (seconds): $($CycleTime.TotalSeconds)" | out-host + "$((Get-Date).ToString()) - Cycle Time (seconds): $($CycleTime.TotalSeconds)" | out-host If ($variables.DonationRunning) { - $Variables.StatusText = "Waiting $($Variables.TimeToSleep) seconds... | Next refresh: $((Get-Date).AddSeconds($Variables.TimeToSleep)) | Donation cycle. Thanks for your support!" + $Variables.StatusText = "Waiting $($Variables.TimeToSleep) seconds... | Next refresh: $((Get-Date).AddSeconds($Variables.TimeToSleep)) | Donation running. Thanks for your support!" } else { $Variables.StatusText = "Waiting $($Variables.TimeToSleep) seconds... | Next refresh: $((Get-Date).AddSeconds($Variables.TimeToSleep))" + $Variables.StatusText = "!! Check out the new server features and remote management web interface !!" } - $Variables | Add-Member -Force @{EndLoop = $True} + $Variables.EndLoop = $True # Sleep $Variables.TimeToSleep # } # $Variables.BrainJobs | foreach { $_ | stop-job | remove-job } # $Variables.BrainJobs = @() # $pid | out-host # $Variables | convertto-json | out-file ".\logs\variables.json" -Remove-Variable Stats,Miners_Type_Combos,Miners_Index_Combos,Miners_Device_Combos,BestMiners_Combos,BestMiners_Combos_Comparison,AllPools,Pools,Miner_Pools,Miner_Pools_Comparison,Miner_Profits,Miner_Profits_Comparison,Miner_Profits_Bias,Miner +# Remove-Variable Stats,Miners_Type_Combos,Miners_Index_Combos,Miners_Device_Combos,BestMiners_Combos,BestMiners_Combos_Comparison,AllPools,Pools,Miner_Pools,Miner_Pools_Comparison,Miner_Profits,Miner_Profits_Comparison,Miner_Profits_Bias,Miner # Get-Variable | out-file ".\logs\variables.txt" # $StackTrace | convertto-json | out-file ".\logs\stacktrace.json" # remove-variable variables diff --git a/Includes/EarningsTracker.ps1 b/Includes/EarningsTracker.ps1 new file mode 100644 index 0000000..ca919a1 --- /dev/null +++ b/Includes/EarningsTracker.ps1 @@ -0,0 +1,301 @@ +<# +This file is part of NPlusMiner +Copyright (c) 2018-2019 MrPlus + +NPlusMiner is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +NPlusMiner is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +#> + +<# +Product: NPlusMiner +File: EarningsTrackerJob.ps1 +version: 5.4.1 +version date: 20190809 +#> +Start-Transcript ".\logs\ET.log" +$pwd | out-host + +# To start the job one could use the following +# $job = Start-Job -FilePath .\EarningTrackerJob.ps1 -ArgumentList $params +# Remove progress info from job.childjobs.Progress to avoid memory leak +$ProgressPreference="SilentlyContinue" + +# Fix TLS version erroring +[Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" + +# Set Process Priority +(Get-Process -Id $PID).PriorityClass = "BelowNormal" + +# $args[0].GetEnumerator() | ForEach-Object { New-Variable -Name $_.Key -Value $_.Value } + +# If ($WorkingDirectory) {Set-Location $WorkingDirectory} +# Start-Transcript ".\Logs\EarnTR.txt" +If (Test-Path ".\logs\EarningTrackerData.json") {$AllBalanceObjectS = Get-Content ".\logs\EarningTrackerData.json" | ConvertFrom-JSON} else {$AllBalanceObjectS = @()} + +. .\Includes\include.ps1 +# $Config = Load-Config ".\Config\Config.json" + # If ($Config.Server_Client) { + # $ServerClientPasswd = ConvertTo-SecureString $Config.Server_ClientPassword -AsPlainText -Force + # $ServerClientCreds = New-Object System.Management.Automation.PSCredential ($Config.Server_ClientUser, $ServerClientPasswd) + # $Variables = [hashtable]::Synchronized(@{}) + # $Variables | Add-Member -Force @{ServerClientCreds = $ServerClientCreds} + # } +$BalanceObjectS = @() +$TrustLevel = 0 +$StartTime = Get-Date +$LastAPIUpdateTime = Get-Date +# $Variables | Add-Member @{EarningsObjects = [PSCustomObject]@{}} + +while ($true) { + if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1; RegisterLoaded(".\Includes\include.ps1")} + + If ($Config.Server_Client) { + $Variables | Add-Member -Force @{ServerRunning = Try{ ((Invoke-WebRequest "http://$($Config.Server_ClientIP):$($Config.Server_ClientPort)/ping" -Credential $Variables.ServerClientCreds -TimeoutSec 3).content -eq "Server Alive")} Catch {$False} } + } + +# Set decimal separator so CSV files look good. + [System.Threading.Thread]::CurrentThread.CurrentUICulture.NumberFormat.NumberDecimalSeparator = "." + [System.Threading.Thread]::CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator = "." + +#Read Config (ie. Pools to track) + $EarningsTrackerConfig = Get-content ".\config\EarningTrackerConfig.json" | ConvertFrom-JSON + $Interval = $EarningsTrackerConfig.PollInterval + +#Filter pools variants + $TrackPools = (($EarningsTrackerConfig.pools | sort -Unique).replace("plus","")).replace("24hr","") + +# Get pools api ref + If (-not $poolapi -or ($LastAPIUpdateTime -le (Get-Date).AddDays(-1))){ + try { + $poolapi = Invoke-ProxiedWebRequest "http://tiny.cc/l355qy" | ConvertFrom-Json} catch {$poolapi = Get-content ".\Config\poolapiref.json" | Convertfrom-json} + $LastAPIUpdateTime = Get-Date + } else { + $poolapi = Get-content ".\Config\poolapiref.json" | Convertfrom-json + } + +#For each pool in config +#Go loop + If (-not $Variables.DonationRunning) { + foreach ($Pool in $TrackPools) { + if ($poolapi -ne $null) { + $poolapi | ConvertTo-json | Out-File ".\Config\poolapiref.json" + If (($poolapi | ? {$_.Name -eq $pool}).EarnTrackSupport -eq "yes") { + $APIUri = ($poolapi | ? {$_.Name -eq $pool}).WalletUri + $PaymentThreshold = ($poolapi | ? {$_.Name -eq $pool}).PaymentThreshold + $BalanceJson = ($poolapi | ? {$_.Name -eq $pool}).Balance + $TotalJson = ($poolapi | ? {$_.Name -eq $pool}).Total + + $ConfName = if ($PoolsConfig.$Pool -ne $Null){$Pool}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + + $Wallet = + if($Pool -eq "miningpoolhub"){ + $PoolConf.APIKey + } else { + $PoolConf.Wallet + } + + $CurDate = Get-Date + # Write-host $Pool + Write-Host "$($APIUri)$($Wallet)" + If ($Pool -eq "nicehash-V1"){ + try { + $TempBalanceData = Invoke-ProxiedWebRequest ("$($APIUri)$($Wallet)") | ConvertFrom-Json } catch { } + if (-not $TempBalanceData.$BalanceJson) {$TempBalanceData | Add-Member -NotePropertyName $BalanceJson -NotePropertyValue ($TempBalanceData.result.Stats | measure -sum $BalanceJson).sum -Force} + if (-not $TempBalanceData.$TotalJson) {$TempBalanceData | Add-Member -NotePropertyName $TotalJson -NotePropertyValue ($TempBalanceData.result.Stats | measure -sum $BalanceJson).sum -Force} + } elseif ($Pool -eq "nicehash") { + try { + $TempBalanceData = Invoke-ProxiedWebRequest ("$($APIUri)$($Wallet)/rigs") | ConvertFrom-Json + [Double]$NHTotalBalance = [Double]($TempBalanceData.unpaidAmount) + [Double]($TempBalanceData.externalBalance) + $TempBalanceData | Add-Member -NotePropertyName $BalanceJson -NotePropertyValue $NHTotalBalance -Force + $TempBalanceData | Add-Member -NotePropertyName $TotalJson -NotePropertyValue $NHTotalBalance -Force + } catch { } + } elseif ($Pool -eq "miningpoolhub") { + try { + $TempBalanceData = ((((Invoke-ProxiedWebRequest ("$($APIUri)$($Wallet)")).content | ConvertFrom-Json).getuserallbalances).data | Where {$_.coin -eq "bitcoin"}) } catch { }#.confirmed + } else { + try { + $TempBalanceData = Invoke-ProxiedWebRequest ("$($APIUri)$($Wallet)") | ConvertFrom-Json } catch { } + } + + If ($TempBalanceData.$TotalJson -gt 0){ + $BalanceData = $TempBalanceData + $AllBalanceObjectS += [PSCustomObject]@{ + Pool = $Pool + Date = $CurDate + balance = $BalanceData.$BalanceJson + unsold = $BalanceData.unsold + total_unpaid = $BalanceData.total_unpaid + total_paid = $BalanceData.total_paid + total_earned = $BalanceData.$TotalJson + currency = $BalanceData.currency + } + $BalanceObjectS = $AllBalanceObjectS | ? {$_.Pool -eq $Pool} + $BalanceObject = $BalanceObjectS[$BalanceOjectS.Count-1] + If ((($CurDate - ($BalanceObjectS[0].Date)).TotalMinutes) -eq 0) {$CurDate = $CurDate.AddMinutes(1)} + + + + If ((($CurDate - ($BalanceObjectS[0].Date)).TotalDays) -ge 1) { + $Growth1 = $BalanceObject.total_earned - (($BalanceObjectS | ? {$_.Date -ge $CurDate.AddHours(-1)}).total_earned | measure -Minimum).Minimum + $Growth6 = $BalanceObject.total_earned - (($BalanceObjectS | ? {$_.Date -ge $CurDate.AddHours(-6)}).total_earned | measure -Minimum).Minimum + $Growth24 = $BalanceObject.total_earned - (($BalanceObjectS | ? {$_.Date -ge $CurDate.AddDays(-1)}).total_earned | measure -Minimum).Minimum + } + If ((($CurDate - ($BalanceObjectS[0].Date)).TotalDays) -lt 1) { + $Growth1 = $BalanceObject.total_earned - (($BalanceObjectS | ? {$_.Date -ge $CurDate.AddHours(-1)}).total_earned | measure -Minimum).Minimum + $Growth6 = $BalanceObject.total_earned - (($BalanceObjectS | ? {$_.Date -ge $CurDate.AddHours(-6)}).total_earned | measure -Minimum).Minimum + $Growth24 = (($BalanceObject.total_earned - $BalanceObjectS[0].total_earned) / ($CurDate - ($BalanceObjectS[0].Date)).TotalHours)*24 + } + If ((($CurDate - ($BalanceObjectS[0].Date)).TotalHours) -lt 6) { + $Growth1 = $BalanceObject.total_earned - (($BalanceObjectS | ? {$_.Date -ge $CurDate.AddHours(-1)}).total_earned | measure -Minimum).Minimum + $Growth6 = (($BalanceObject.total_earned - $BalanceObjectS[0].total_earned) / ($CurDate - ($BalanceObjectS[0].Date)).TotalHours)*6 + } + If ((($CurDate - ($BalanceObjectS[0].Date)).TotalHours) -lt 1) { + $Growth1 = (($BalanceObject.total_earned - $BalanceObjectS[0].total_earned) / ($CurDate - ($BalanceObjectS[0].Date)).TotalMinutes)*60 + } + + $AvgBTCHour = If ((($CurDate - ($BalanceObjectS[0].Date)).TotalHours) -ge 1) {(($BalanceObject.total_earned - $BalanceObjectS[0].total_earned) / ($CurDate - ($BalanceObjectS[0].Date)).TotalHours)} else {$Growth1} + + $EarningsObject = [PSCustomObject]@{ + Pool = $pool + Wallet = $Wallet + Date = $CurDate + StartTime = $BalanceObjectS[0].Date + balance = $BalanceObject.balance + unsold = $BalanceObject.unsold + total_unpaid = $BalanceObject.total_unpaid + total_paid = $BalanceObject.total_paid + total_earned = $BalanceObject.total_earned + currency = $BalanceObject.currency + GrowthSinceStart = $BalanceObject.total_earned - $BalanceObjectS[0].total_earned + Growth1 = $Growth1 + Growth6 = $Growth6 + Growth24 = $Growth24 + AvgHourlyGrowth = $AvgBTCHour + BTCD = $AvgBTCHour*24 + EstimatedEndDayGrowth = If ((($CurDate - ($BalanceObjectS[0].Date)).TotalHours) -ge 1) {($AvgBTCHour * ((Get-Date -Hour 0 -Minute 00 -Second 00).AddDays(1).AddSeconds(-1) - $CurDate).Hours)} else {$Growth1 * ((Get-Date -Hour 0 -Minute 00 -Second 00).AddDays(1).AddSeconds(-1) - $CurDate).Hours} + EstimatedPayDate = if ($PaymentThreshold){IF ($BalanceObject.balance -lt $PaymentThreshold) {If ($AvgBTCHour -gt 0.0000000000000001) {$CurDate.AddHours(($PaymentThreshold - $BalanceObject.balance) / ($AvgBTCHour))} Else {"Unknown"}} else {"Next Payout !"}}else{"Unknown"} + TrustLevel = if(($CurDate - ($BalanceObjectS[0].Date)).TotalMinutes -le 360){($CurDate - ($BalanceObjectS[0].Date)).TotalMinutes/360}else{1} + PaymentThreshold = $PaymentThreshold + TotalHours = ($CurDate - ($BalanceObjectS[0].Date)).TotalHours + } + # "$($EarningsObject.Pool): $($EarningsObject.balance)" | out-host + + if (!$Variables.Earnings) {$Variables | Add-Member -Force @{Earnings = @()}} + $EarningsTemp = @($Variables.Earnings | ? {$_.Pool -ne $Pool}).Clone() + $EarningsTemp += $EarningsObject + $Variables.Earnings = $EarningsTemp.Clone() + + if ($EarningsTrackerConfig.EnableLog){$EarningsObject | Export-Csv -NoTypeInformation -Append ".\Logs\EarningTrackerLog.csv"} + + If (Test-Path ".\Logs\DailyEarnings.csv") { + $DailyEarnings = Import-Csv ".\Logs\DailyEarnings.csv" # Add filter on mw # days from config. + If ($DailyEarnings | ? {$_.Date -eq $CurDate.ToString("MM/dd/yyyy") -and $_.Pool -eq $Pool}) { + $DailyEarnings | select Date,Pool, + @{Name="DailyEarnings";Expression={ + If ($_.Date -eq ($CurDate.ToString("MM/dd/yyyy")) -and $_.Pool -eq $Pool) { + If ($_.PrePaimentDayValue -gt 0) { + #Paiment occured + ($_.PrePaimentDayValue - $_.FirstDayValue) + ($BalanceObject.total_earned - (($BalanceObjectS | ? {$_.Date.DayOfYear -eq $CurDate.DayOfYear}).total_earned | measure -minimum).minimum) + } else { + $BalanceObject.total_earned - (($BalanceObjectS | ? {$_.Date.DayOfYear -eq $CurDate.DayOfYear}).total_earned | measure -minimum).minimum + } + } else {$_.DailyEarnings} + }}, + FirstDayDate, + FirstDayValue, + @{Name="LastDayDate";Expression={ + If ($_.Date -eq ($CurDate.ToString("MM/dd/yyyy")) -and $_.Pool -eq $Pool) { + $BalanceObject.Date + } else {$_.LastDayDate} + }}, + @{Name="LastDayValue";Expression={ + If ($_.Date -eq ($CurDate.ToString("MM/dd/yyyy")) -and $_.Pool -eq $Pool) { + $BalanceObject.total_earned + } else {$_.LastDayValue} + }}, + @{Name="PrePaimentDayValue";Expression={ + If (($_.Date -eq ($CurDate.ToString("MM/dd/yyyy")) -and $_.Pool -eq $Pool) -and ($BalanceObject.total_earned -lt ($BalanceObjectS[$BalanceObjectS.Count-2].total_earned/2))) { + $BalanceObjectS[$BalanceObjectS.Count-2].total_earned + } else {$_.PrePaimentDayValue} + }}, + @{Name="Balance";Expression={ + If ($_.Date -eq ($CurDate.ToString("MM/dd/yyyy")) -and $_.Pool -eq $Pool) { + $BalanceObject.balance + } else {$_.Balance} + }}, + @{Name="BTCD";Expression={ + If ($_.Date -eq ($CurDate.ToString("MM/dd/yyyy")) -and $_.Pool -eq $Pool) { + $BalanceObject.Growth24 + } else {$_.BTCD} + }} | Export-Csv ".\Logs\DailyEarnings.csv" -NoTypeInformation + } else { + $DailyEarnings = [PSCustomObject]@{ + Date = $CurDate.ToString("MM/dd/yyyy") + Pool = $Pool + DailyEarnings = $BalanceObject.total_earned - (($BalanceObjectS | ? {$_.Date.DayOfYear -eq $CurDate.DayOfYear}).total_earned | measure -minimum).minimum + FirstDayDate = $BalanceObject.Date + FirstDayValue = $BalanceObject.total_earned + LastDayDate = $BalanceObject.Date + LastDayValue = $BalanceObject.total_earned + PrePaimentDayValue = 0 + Balance = $BalanceObject.Balance + BTCD = $BalanceObject.Growth24 + } + $DailyEarnings | Export-Csv ".\Logs\DailyEarnings.csv" -NoTypeInformation -Append + } + + } else { + $DailyEarnings = [PSCustomObject]@{ + Date = $CurDate.ToString("MM/dd/yyyy") + Pool = $Pool + DailyEarnings = $BalanceObject.total_earned - (($BalanceObjectS | ? {$_.Date.DayOfYear -eq $CurDate.DayOfYear}).total_earned | measure -minimum).minimum + FirstDayDate = $BalanceObject.Date + FirstDayValue = $BalanceObject.total_earned + LastDayDate = $BalanceObject.Date + LastDayValue = $BalanceObject.total_earned + PrePaimentDayValue = 0 + Balance = $BalanceObject.Balance + BTCD = $BalanceObject.Growth24 + } + $DailyEarnings | Export-Csv ".\Logs\DailyEarnings.csv" -NoTypeInformation + } + rv DailyEarnings + + # Some pools do reset "Total" after payment (zpool) + # Results in showing bad negative earnings + # Detecting if current is more than 50% less than previous and reset history if so + If ($BalanceObject.total_earned -lt ($BalanceObjectS[$BalanceObjectS.Count-2].total_earned/2)){$AllBalanceObjectS=$AllBalanceObjectS | ? {$_.Pool -ne $Pool};$AllBalanceObjectS += $BalanceObject} + rv TempBalanceData + } #else {$Pool | Out-Host} #else {return} + } + } + } + } + + If ($AllBalanceObjectS.Count -gt 1) {$AllBalanceObjectS = $AllBalanceObjectS | ? {$_.Date -ge $CurDate.AddDays(-1).AddHours(-1)}} + # Save data only at defined interval. Limit disk access + If ((Get-Date) -gt $WriteAt) { + $WriteAt = (Get-Date).AddMinutes($EarningsTrackerConfig.WriteEvery) + if ($AllBalanceObjectS.Count -gt 1) {$AllBalanceObjectS | ConvertTo-JSON | Out-File ".\logs\EarningTrackerData.json"} + } + + + # Sleep until next update based on $Interval. Modulo $Interval. + # Sleep (60*($Interval-((get-date).minute%$Interval))) # Changed to avoid pool API load. + If (($EarningsObject.Date - $EarningsObject.StartTime).TotalMinutes -le 20){ + Sleep (60*($Interval/2)) + }else{ + Sleep (60*($Interval)) + } +} diff --git a/Includes/EarningsTrackerJob.ps1 b/Includes/EarningsTrackerJob.ps1 index ad2d50d..0841ef5 100644 --- a/Includes/EarningsTrackerJob.ps1 +++ b/Includes/EarningsTrackerJob.ps1 @@ -40,12 +40,27 @@ If ($WorkingDirectory) {Set-Location $WorkingDirectory} # Start-Transcript ".\Logs\EarnTR.txt" If (Test-Path ".\logs\EarningTrackerData.json") {$AllBalanceObjectS = Get-Content ".\logs\EarningTrackerData.json" | ConvertFrom-JSON} else {$AllBalanceObjectS = @()} +. .\Includes\include.ps1 +$Config = Load-Config ".\Config\Config.json" + If ($Config.Server_Client) { + $ServerClientPasswd = ConvertTo-SecureString $Config.Server_ClientPassword -AsPlainText -Force + $ServerClientCreds = New-Object System.Management.Automation.PSCredential ($Config.Server_ClientUser, $ServerClientPasswd) + $Variables = [hashtable]::Synchronized(@{}) + $Variables | Add-Member -Force @{ServerClientCreds = $ServerClientCreds} + } $BalanceObjectS = @() $TrustLevel = 0 $StartTime = Get-Date $LastAPIUpdateTime = Get-Date while ($true) { + If ($Config.Server_Client) { + $Variables | Add-Member -Force @{ServerRunning = Try{ ((Invoke-WebRequest "http://$($Config.Server_ClientIP):$($Config.Server_ClientPort)/ping" -Credential $Variables.ServerClientCreds -TimeoutSec 3).content -eq "Server Alive")} Catch {$False} } + } + +# Set decimal separator so CSV files look good. + [System.Threading.Thread]::CurrentThread.CurrentUICulture.NumberFormat.NumberDecimalSeparator = "." + [System.Threading.Thread]::CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator = "." #Read Config (ie. Pools to track) $EarningsTrackerConfig = Get-content ".\config\EarningTrackerConfig.json" | ConvertFrom-JSON @@ -55,13 +70,13 @@ while ($true) { $TrackPools = (($EarningsTrackerConfig.pools | sort -Unique).replace("plus","")).replace("24hr","") # Get pools api ref - If (-not $poolapi -or ($LastAPIUpdateTime -le (Get-Date).AddDays(-1))){ - try { - $poolapi = Invoke-WebRequest "http://tiny.cc/l355qy" -TimeoutSec 15 -UseBasicParsing -Headers @{"Cache-Control"="no-cache"} | ConvertFrom-Json} catch {$poolapi = Get-content ".\Config\poolapiref.json" | Convertfrom-json} - $LastAPIUpdateTime = Get-Date - } else { - $poolapi = Get-content ".\Config\poolapiref.json" | Convertfrom-json - } + If (-not $poolapi -or ($LastAPIUpdateTime -le (Get-Date).AddDays(-1))){ + try { + $poolapi = Invoke-ProxiedWebRequest "http://tiny.cc/l355qy" | ConvertFrom-Json} catch {$poolapi = Get-content ".\Config\poolapiref.json" | Convertfrom-json} + $LastAPIUpdateTime = Get-Date + } else { + $poolapi = Get-content ".\Config\poolapiref.json" | Convertfrom-json + } #For each pool in config #Go loop @@ -78,43 +93,49 @@ while ($true) { $PoolConf = $PoolsConfig.$ConfName $Wallet = - if($Pool -eq "miningpoolhub"){ + if($Pool -in @("miningpoolhub","prohashing")){ $PoolConf.APIKey } else { $PoolConf.Wallet } $CurDate = Get-Date - # Write-host $Pool - # Write-Host "$($APIUri)$($Wallet)" + # Write-host $Pool + # Write-Host "$($APIUri)$($Wallet)" If ($Pool -eq "nicehash-V1"){ try { - $TempBalanceData = Invoke-WebRequest ("$($APIUri)$($Wallet)") -TimeoutSec 15 -UseBasicParsing -Headers @{"Cache-Control"="no-cache"} | ConvertFrom-Json } catch { } + $TempBalanceData = Invoke-ProxiedWebRequest ("$($APIUri)$($Wallet)") -UseBasicParsing | ConvertFrom-Json } catch { } if (-not $TempBalanceData.$BalanceJson) {$TempBalanceData | Add-Member -NotePropertyName $BalanceJson -NotePropertyValue ($TempBalanceData.result.Stats | measure -sum $BalanceJson).sum -Force} if (-not $TempBalanceData.$TotalJson) {$TempBalanceData | Add-Member -NotePropertyName $TotalJson -NotePropertyValue ($TempBalanceData.result.Stats | measure -sum $BalanceJson).sum -Force} } elseif ($Pool -eq "nicehash") { try { - $TempBalanceData = Invoke-WebRequest ("$($APIUri)$($Wallet)/rigs") -TimeoutSec 15 -UseBasicParsing -Headers @{"Cache-Control"="no-cache"} | ConvertFrom-Json } catch { } + $TempBalanceData = Invoke-ProxiedWebRequest ("$($APIUri)$($Wallet)/rigs2") -UseBasicParsing | ConvertFrom-Json } catch { } [Double]$NHTotalBalance = [Double]($TempBalanceData.unpaidAmount) + [Double]($TempBalanceData.externalBalance) $TempBalanceData | Add-Member -NotePropertyName $BalanceJson -NotePropertyValue $NHTotalBalance -Force $TempBalanceData | Add-Member -NotePropertyName $TotalJson -NotePropertyValue $NHTotalBalance -Force + $TempBalanceData | Add-Member -NotePropertyName "currency" -NotePropertyValue "BTC" -Force } elseif ($Pool -eq "miningpoolhub") { try { - $TempBalanceData = ((((Invoke-WebRequest ("$($APIUri)$($Wallet)") -TimeoutSec 15 -UseBasicParsing -Headers @{"Cache-Control"="no-cache"}).content | ConvertFrom-Json).getuserallbalances).data | Where {$_.coin -eq "bitcoin"}) } catch { }#.confirmed + $TempBalanceData = ((((Invoke-ProxiedWebRequest ("$($APIUri)$($Wallet)") -UseBasicParsing).content | ConvertFrom-Json).getuserallbalances).data | Where {$_.coin -eq "bitcoin"}) } catch { }#.confirmed + $TempBalanceData | Add-Member -NotePropertyName "currency" -NotePropertyValue "BTC" -Force + } elseif ($Pool -eq "prohashing") { + try { + $TempBalanceData = (((Invoke-ProxiedWebRequest ("$($APIUri)$($Wallet)") -UseBasicParsing).content | ConvertFrom-Json).data.balances.($Config.Passwordcurrency)) } catch { }#.confirmed + $TempBalanceData | Add-Member -NotePropertyName "currency" -NotePropertyValue $Config.Passwordcurrency -Force } else { try { - $TempBalanceData = Invoke-WebRequest ("$($APIUri)$($Wallet)") -TimeoutSec 15 -UseBasicParsing -Headers @{"Cache-Control"="no-cache"} | ConvertFrom-Json } catch { } + $TempBalanceData = Invoke-ProxiedWebRequest ("$($APIUri)$($Wallet)") -UseBasicParsing | ConvertFrom-Json } catch { } } If ($TempBalanceData.$TotalJson -gt 0){ $BalanceData = $TempBalanceData $AllBalanceObjectS += [PSCustomObject]@{ Pool = $Pool Date = $CurDate - balance = $BalanceData.$BalanceJson - unsold = $BalanceData.unsold - total_unpaid = $BalanceData.total_unpaid - total_paid = $BalanceData.total_paid - total_earned = $BalanceData.$TotalJson + balance = [Math]::Round($BalanceData.$BalanceJson, 8) + unsold = [Math]::Round($BalanceData.unsold, 8) + total_unpaid = [Math]::Round($BalanceData.total_unpaid, 8) + total_paid = [Math]::Round($BalanceData.total_paid, 8) + total_earned = [Math]::Round($BalanceData.$TotalJson, 8) currency = $BalanceData.currency } $BalanceObjectS = $AllBalanceObjectS | ? {$_.Pool -eq $Pool} @@ -160,12 +181,12 @@ while ($true) { AvgHourlyGrowth = $AvgBTCHour BTCD = $AvgBTCHour*24 EstimatedEndDayGrowth = If ((($CurDate - ($BalanceObjectS[0].Date)).TotalHours) -ge 1) {($AvgBTCHour * ((Get-Date -Hour 0 -Minute 00 -Second 00).AddDays(1).AddSeconds(-1) - $CurDate).Hours)} else {$Growth1 * ((Get-Date -Hour 0 -Minute 00 -Second 00).AddDays(1).AddSeconds(-1) - $CurDate).Hours} - EstimatedPayDate = if ($PaymentThreshold){IF ($BalanceObject.balance -lt $PaymentThreshold) {If ($AvgBTCHour -gt 0) {$CurDate.AddHours(($PaymentThreshold - $BalanceObject.balance) / $AvgBTCHour)} Else {"Unknown"}} else {"Next Payout !"}}else{"Unknown"} + EstimatedPayDate = if ($PaymentThreshold){IF ($BalanceObject.balance -lt $PaymentThreshold) {If ($AvgBTCHour -gt 0.0000000000000001) {$CurDate.AddHours(($PaymentThreshold - $BalanceObject.balance) / ($AvgBTCHour))} Else {"Unknown"}} else {"Next Payout !"}}else{"Unknown"} TrustLevel = if(($CurDate - ($BalanceObjectS[0].Date)).TotalMinutes -le 360){($CurDate - ($BalanceObjectS[0].Date)).TotalMinutes/360}else{1} PaymentThreshold = $PaymentThreshold TotalHours = ($CurDate - ($BalanceObjectS[0].Date)).TotalHours } - + $EarningsObject if ($EarningsTrackerConfig.EnableLog){$EarningsObject | Export-Csv -NoTypeInformation -Append ".\Logs\EarningTrackerLog.csv"} @@ -268,5 +289,4 @@ while ($true) { }else{ Sleep (60*($Interval)) } - } diff --git a/Includes/Include.ps1 b/Includes/Include.ps1 index 6e25397..eeba238 100644 --- a/Includes/Include.ps1 +++ b/Includes/Include.ps1 @@ -46,7 +46,7 @@ function Get-MemoryUsage { If (!$Variables.PerfCounterProcPath) { $proc_path=((Get-Counter "\Process(*)\ID Process").CounterSamples | ? {$_.RawValue -eq $pid}).Path $proc_path = $proc_path -replace "id process","Working Set - Private" - $Variables | Add-Member -Force @{PerfCounterProcPath = $proc_path} + $Variables.PerfCounterProcPath = $proc_path } # Write-Host -Object ('PagedMemorySize : {0:n1} MB ' -f ($P.PagedMemorySize/1024)) @@ -65,7 +65,7 @@ Function GetNVIDIADriverVersion { Function Global:RegisterLoaded ($File) { New-Item -Path function: -Name script:"$((Get-FileHash (Resolve-Path $File)).Hash)" -Value {$true} -EA SilentlyContinue | Add-Member @{"File" = (Resolve-Path $File).Path} -EA SilentlyContinue - $Variables.StatusText = "File loaded - $($file) - $((Get-PSCallStack).Command[1])" + # $Variables.StatusText = "File loaded - $($file) - $((Get-PSCallStack).Command[1])" } Function Global:IsLoaded ($File) { @@ -159,7 +159,7 @@ namespace PInvoke.Win32 { Start-Sleep 1 } } ) | Out-Null - $Variables | Add-Member -Force @{IdleRunspaceHandle = $idlePowershell.BeginInvoke()} + $Variables.IdleRunspaceHandle = $idlePowershell.BeginInvoke() } Function Update-Monitoring { @@ -172,11 +172,11 @@ Function Update-Monitoring { If ($Config.ReportToServer) { $Version = "$($Variables.CurrentProduct) $($Variables.CurrentVersion.ToString())" $Status = If ($Variables.Paused) { "Paused" } else { "Running" } - $RunningMiners = $Variables.ActiveMinerPrograms | Where-Object {$_.Status -eq "Running"} + $RunningMiners = $Variables.ActiveMinerPrograms.Where( {$_.Status -eq "Running"} ) # Add the associated object from $Variables.Miners since we need data from that too $RunningMiners | Foreach-Object { $RunningMiner = $_ - $Miner = $Variables.Miners | Where-Object {$_.Name -eq $RunningMiner.Name -and $_.Path -eq $RunningMiner.Path -and $_.Arguments -eq $RunningMiner.Arguments} + $Miner = $Variables.Miners.Where( {$_.Name -eq $RunningMiner.Name -and $_.Path -eq $RunningMiner.Path -and $_.Arguments -eq $RunningMiner.Arguments} ) $_ | Add-Member -Force @{'Miner' = $Miner} } @@ -238,8 +238,8 @@ Function Update-Monitoring { } } - $Variables | Add-Member -Force @{Workers = $Workers} - $Variables | Add-Member -Force @{WorkersLastUpdated = (Get-Date)} + $Variables.Workers = $Workers + $Variables.WorkersLastUpdated = (Get-Date) } Catch { $Variables.StatusText = "Unable to retrieve worker data from $($Config.MonitoringServer)" @@ -257,7 +257,7 @@ Function Start-Mining { $CycleRunspace.SessionStateProxy.Path.SetLocation((Split-Path $script:MyInvocation.MyCommand.Path)) $Global:powershell = [powershell]::Create() $powershell.Runspace = $CycleRunspace - $powershell.AddScript( { + $scriptblock = { #Start the log Start-Transcript -Path ".\logs\CoreCyle-$((Get-Date).ToString('yyyyMMdd')).log" -Append -Force # Purge Logs more than 10 days @@ -276,17 +276,17 @@ Function Start-Mining { # Keep updating exchange rate $Rates = Invoke-RestMethod "https://api.coinbase.com/v2/exchange-rates?currency=BTC" -TimeoutSec 15 -UseBasicParsing | Select-Object -ExpandProperty data | Select-Object -ExpandProperty rates - $Config.Currency | Where-Object {$Rates.$_} | ForEach-Object {$Rates | Add-Member $_ ([Double]$Rates.$_) -Force} - $Variables | Add-Member -Force @{Rates = $Rates} + $Config.Currency.Where( {$Rates.$_} ) | ForEach-Object {$Rates | Add-Member $_ ([Double]$Rates.$_) -Force} + $Variables.Rates = $Rates # Update the UI every 30 seconds, and the Last 1/6/24hr and text window every 2 minutes for ($i = 0; $i -lt 4; $i++) { if ($i -eq 3) { - $Variables | Add-Member -Force @{EndLoop = $True} + $Variables.EndLoop = $True Update-Monitoring } else { - $Variables | Add-Member -Force @{EndLoop = $False} + $Variables.EndLoop = $False } $Variables.StatusText = "Mining paused" @@ -299,9 +299,12 @@ Function Start-Mining { Sleep $Variables.TimeToSleep } } - }) | Out-Null - $Variables | add-Member -Force @{CycleRunspaceHandle = $powershell.BeginInvoke()} - $Variables | Add-Member -Force @{LastDonated = (Get-Date).AddDays(-1).AddHours(1)} + } + $powershell.AddScript( {. $args[0]} ) | Out-Null + $powershell.AddArgument($scriptblock.Ast.GetScriptBlock()) + + $Variables.CycleRunspaceHandle = $powershell.BeginInvoke() + $Variables.LastDonated = (Get-Date).AddDays(-1).AddHours(1) } Function Stop-Mining { @@ -391,7 +394,23 @@ Function Load-Config { }} } } + + If ($Config.Server_Password -in @("","3890292d-990c-44ba-b779-552829fc0bc4")) { + $Guid = (New-Guid).Guid + $Config.Server_Password = $Guid + If ($Config.Server_ClientPassword -in @("","3890292d-990c-44ba-b779-552829fc0bc4")) { + $Config.Server_ClientPassword = $Guid + } + } + if ($Variables) { + $ServerPasswd = ConvertTo-SecureString $Config.Server_Password -AsPlainText -Force + $ServerCreds = New-Object System.Management.Automation.PSCredential ($Config.Server_User, $ServerPasswd) + $Variables.ServerCreds = $ServerCreds + $ServerClientPasswd = ConvertTo-SecureString $Config.Server_ClientPassword -AsPlainText -Force + $ServerClientCreds = New-Object System.Management.Automation.PSCredential ($Config.Server_ClientUser, $ServerClientPasswd) + $Variables.ServerClientCreds = $ServerClientCreds + } $Config } } @@ -405,6 +424,9 @@ Function Write-Config { ) If ($Config.ManualConfig) {Update-Status("Manual config mode - Not saving config"); return} If ($Config -ne $null) { + If (@($Config.Algorithm).Where({$_.StartsWith("+")})) { $Config | Add-Member -Force @{AlgoInclude = @(@($Config.Algorithm).Where({$_.StartsWith("+")}).substring(1) | ForEach {Get-Algorithm $_})} } else {$Config | Add-Member -Force @{AlgoInclude = @()}} + If (@($Config.Algorithm).Where({$_.StartsWith("-")})) { $Config | Add-Member -Force @{AlgoExclude = @(@($Config.Algorithm).Where({$_.StartsWith("-")}).substring(1) | ForEach {Get-Algorithm $_})} } else {$Config | Add-Member -Force @{AlgoExclude = @()}} + if (Test-Path $ConfigFile) {Copy-Item $ConfigFile "$($ConfigFile).backup"} $OrderedConfig = [PSCustomObject]@{}; ($config | select -Property * -ExcludeProperty PoolsConfig) | % {$_.psobject.properties | sort Name | % {$OrderedConfig | Add-Member -Force @{$_.Name = $_.Value}}} $OrderedConfig | ConvertTo-json | out-file $ConfigFile @@ -574,13 +596,33 @@ function Get-ChildItemContent { $PropertyKeys | ForEach-Object { if ($Property.$_ -is [String]) { $Property.$_ = Invoke-Expression "`"$($Property.$_)`"" - } + } } } } } $ChildItems } + +function Get-SubScriptContent { + param( + [Parameter(Mandatory = $true)] + [String]$Path, + [Parameter(Mandatory = $false)] + [Array]$Include = @() + ) + [System.Collections.ArrayList]$Content = @() + $ChildItems = Get-ChildItem -Recurse -Path $Path -Include $Include | ForEach-Object { + $Name = $_.BaseName + $FileName = $_.Name + if ($_.Extension -eq ".ps1") { + &$_.FullName | ForEach-Object {$Content.Add([PSCustomObject]@{Name = $Name; Content = $_}) > $null } + # &$_.FullName | ForEach-Object {$Content += [PSCustomObject]@{Name = $Name; Content = $_} } + } + } + $Content +} + function Invoke_TcpRequest { param( @@ -746,6 +788,13 @@ function Get-HashRate { $Data = $Request | ConvertFrom-Json $HashRate = [Double]($Data.devices.speed | Measure-Object -Sum).Sum } + "gminerdual" { + $Message = @{id = 1; method = "getstat" } | ConvertTo-Json -Compress + $Request = Invoke_httpRequest $Server $Port "/stat" 5 + $Data = $Request | ConvertFrom-Json + $HashRate = [Double]($Data.devices.speed2 | Measure-Object -Sum).Sum + $HashRate_Dual = [Double]($Data.devices.speed | Measure-Object -Sum).Sum + } "claymore" { $Request = Invoke_httpRequest $Server $Port "" 5 @@ -765,7 +814,7 @@ function Get-HashRate { $HashRate = [int](($Data.result[2] -split ';')[0]) * 1000 } } - + "TTminer" { $Parameters = @{id = 1; jsonrpc = "2.0"; method = "miner_getstat1"} | ConvertTo-Json -Compress @@ -808,7 +857,7 @@ function Get-HashRate { $Request = Invoke_TcpRequest $server $port $message 5 $Data = $Request | ConvertFrom-Json $HashRate = [Double](($Data.result.speed_sps) | Measure-Object -Sum).Sum - } + } "wrapper" { $HashRate = "" @@ -858,11 +907,29 @@ function Get-HashRate { } } + "NBMinerdualEaglesong" { + $Request = Invoke_httpRequest $Server $Port "/api/v1/status" 5 + if ($Request) { + $Data = $Request | ConvertFrom-Json + $HashRate = [double]$Data.miner.total_hashrate_raw + $HashRate_Dual = [double]$Data.miner.total_hashrate2_raw + } + } + + "NBMinerdualHandshake" { + $Request = Invoke_httpRequest $Server $Port "/api/v1/status" 5 + if ($Request) { + $Data = $Request | ConvertFrom-Json + $HashRate = [double]$Data.miner.total_hashrate2_raw + $HashRate_Dual = [double]$Data.miner.total_hashrate_raw + } + } + "LOL" { $Request = Invoke_httpRequest $Server $Port "/summary" 5 if ($Request) { $Data = $Request | ConvertFrom-Json - $HashRate = [Double]$data.Session.Performance_Summary + $HashRate = [Double]$data.Session.Performance_Summary } } @@ -870,7 +937,7 @@ function Get-HashRate { $Request = Invoke_TcpRequest $Server $Port "status" 5 if ($Request) { $Data = $Request | ConvertFrom-Json - $HashRate = [Double]$Data.result.speed_ips * 1000000 + $HashRate = [Double]$Data.result.speed_ips * 1000000 } } @@ -1079,38 +1146,38 @@ function Start-SubProcess { $CreateProcessExitCode = [Kernel32]::CreateProcess($lpApplicationName, $lpCommandLine, [ref] $lpProcessAttributes, [ref] $lpThreadAttributes, $bInheritHandles, $dwCreationFlags, $lpEnvironment, $lpCurrentDirectory, [ref] $lpStartupInfo, [ref] $lpProcessInformation) $x = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() - # Write-Host "CreateProcessExitCode: $CreateProcessExitCode" + # Write-Host "CreateProcessExitCode: $CreateProcessExitCode" # Write-Host "Last error $x" - Write-Host $lpCommandLine - # Write-Host "lpProcessInformation.dwProcessID: $($lpProcessInformation.dwProcessID)" - - If ($CreateProcessExitCode) { - # Write-Host "lpProcessInformation.dwProcessID - WHEN TRUE: $($lpProcessInformation.dwProcessID)" - - $Process = Get-Process -Id $lpProcessInformation.dwProcessID - - # Dirty workaround - # Need to investigate. lpProcessInformation sometimes comes null even if process started - # So getting process with the same FilePath if so - $Tries = 0 - While ($Process -eq $null -and $Tries -le 5) { - Write-Host "Can't get process - $Tries" - $Tries++ - Sleep 1 - $Process = (Get-Process | ? {$_.Path -eq $FilePath})[0] - Write-Host "Process= $($Process.Handle)" - } - - if ($Process -eq $null) { - Write-Host "Case 2 - Failed Get-Process" - [PSCustomObject]@{ProcessId = $null} - return - } - } else { - Write-Host "Case 1 - Failed CreateProcess" - [PSCustomObject]@{ProcessId = $null} - return - } + Write-Host $lpCommandLine + # Write-Host "lpProcessInformation.dwProcessID: $($lpProcessInformation.dwProcessID)" + + If ($CreateProcessExitCode) { + # Write-Host "lpProcessInformation.dwProcessID - WHEN TRUE: $($lpProcessInformation.dwProcessID)" + + $Process = Get-Process -Id $lpProcessInformation.dwProcessID + + # Dirty workaround + # Need to investigate. lpProcessInformation sometimes comes null even if process started + # So getting process with the same FilePath if so + $Tries = 0 + While ($Process -eq $null -and $Tries -le 5) { + Write-Host "Can't get process - $Tries" + $Tries++ + Sleep 1 + $Process = (Get-Process | ? {$_.Path -eq $FilePath})[0] + Write-Host "Process= $($Process.Handle)" + } + + if ($Process -eq $null) { + Write-Host "Case 2 - Failed Get-Process" + [PSCustomObject]@{ProcessId = $null} + return + } + } else { + Write-Host "Case 1 - Failed CreateProcess" + [PSCustomObject]@{ProcessId = $null} + return + } [PSCustomObject]@{ProcessId = $Process.Id; ProcessHandle = $Process.Handle} @@ -1124,7 +1191,7 @@ function Start-SubProcess { do {Start-Sleep 1; $JobOutput = Receive-Job $Job} while ($JobOutput -eq $null) - $Process = Get-Process | Where-Object Id -EQ $JobOutput.ProcessId + $Process = (Get-Process).Where( { $_.Id -EQ $JobOutput.ProcessId } ) $Process.Handle | Out-Null $Process } @@ -1137,6 +1204,7 @@ function Expand-WebRequest { [Parameter(Mandatory = $true)] [String]$Path ) + [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" $FolderName_Old = ([IO.FileInfo](Split-Path $Uri -Leaf)).BaseName $FolderName_New = Split-Path $Path -Leaf @@ -1145,8 +1213,7 @@ function Expand-WebRequest { if (Test-Path $FileName) {Remove-Item $FileName} if (Test-Path "$(Split-Path $Path)\$FolderName_New") {Remove-Item "$(Split-Path $Path)\$FolderName_New" -Recurse -Force} if (Test-Path "$(Split-Path $Path)\$FolderName_Old") {Remove-Item "$(Split-Path $Path)\$FolderName_Old" -Recurse -Force} - - Invoke-WebRequest $Uri -OutFile $FileName -TimeoutSec 15 -UseBasicParsing + Invoke-WebRequest -Uri $Uri -OutFile $FileName -TimeoutSec 15 -UseBasicParsing Start-Process ".\Utils\7z" "x $FileName -o$(Split-Path $Path)\$FolderName_Old -y -spe" -Wait if (Get-ChildItem "$(Split-Path $Path)\$FolderName_Old" | Where-Object PSIsContainer -EQ $false) { Rename-Item "$(Split-Path $Path)\$FolderName_Old" "$FolderName_New" @@ -1163,12 +1230,17 @@ function Get-Algorithm { [Parameter(Mandatory = $true)] [String]$Algorithm ) + + If ((-not $Variables.Algorithms) -or (Get-Date).AddMinutes(-2) -ge $Variables.Algorithms.UpdatedCache ){ + $Variables | Add-Member @{ Algorithms = Get-Content ".\Includes\Algorithms.txt" | ConvertFrom-Json } -Force + $Variables.Algorithms | Add-Member @{ UpdatedCache = get-date } -Force + } - $Algorithms = Get-Content ".\Includes\Algorithms.txt" | ConvertFrom-Json + # $Algorithms = Get-Content ".\Includes\Algorithms.txt" | ConvertFrom-Json $Algorithm = (Get-Culture).TextInfo.ToTitleCase(($Algorithm -replace "-", " " -replace "_", " ")) -replace " " - if ($Algorithms.$Algorithm) {$Algorithms.$Algorithm} + if ($Variables.Algorithms.$Algorithm) {$Variables.Algorithms.$Algorithm} else {$Algorithm} } @@ -1280,7 +1352,7 @@ Function Autoupdate { ls .\OptionalMiners\ | ? {$_.name -notin (ls .\$UpdateFileName\OptionalMiners\).name} | % {Remove-Item -Recurse -Force $_.FullName} # Update Optional Miners to Miners if in use - ls .\OptionalMiners\ | ? {$_.name -in (ls .\Miners\).name} | % {Copy-Item -Force $_.FullName .\Miners\} + # ls .\OptionalMiners\ | ? {$_.name -in (ls .\Miners\).name} | % {Copy-Item -Force $_.FullName .\Miners\} # Remove any obsolete miner file (ie. Not in new version Miners or OptionalMiners) ls .\Miners\ | ? {$_.name -notin (ls .\$UpdateFileName\Miners\).name -and $_.name -notin (ls .\$UpdateFileName\OptionalMiners\).name} | % {Remove-Item -Recurse -Force $_.FullName} @@ -1410,11 +1482,11 @@ Function Merge-Command { Switch ($type) { "Password" { If ($Slave -and !($Slave.StartsWith(","))) {$Slave = ",$($Slave)"} - ($Master.split(",") | ? {$_ -ne ""} | foreach {",$($_)"}) | foreach { + ($Master.split(",").Where({$_ -ne ""}) | foreach {",$($_)"}) | foreach { $MasterPassArg = $_ - ($_.split("=") | ? {$_.startsWith(",")} | foreach {"$($_)="}) | Foreach { + ($_.split("=").Where({$_.startsWith(",")}) | foreach {"$($_)="}) | Foreach { If ($_ -notin $NoReplacePassArgs) { - If ($Slave -match "$_ *([^,]+)" | Out-Null) { + If ($Slave -match "$_ *([^,]+)") { $Slave = $Slave -replace "$_ *([^,]+)",$MasterPassArg } else { $Slave = $Slave + $MasterPassArg @@ -1425,22 +1497,31 @@ Function Merge-Command { $Slave.Substring(1) } "Command" { - If ($Master -and !$Master.StartsWith(" ")) {$Master = " $($Master)"} - If ($Slave -and !$Slave.StartsWith(" ")) {$Slave = " $($Slave)"} - If ($Master -and !$Master.EndsWith(" ")) {$Master = "$($Master) "} - If ($Slave -and !$Slave.EndsWith(" ")) {$Slave = "$($Slave) "} - ($Master.split(" ") | ? {$_.StartsWith("-")}) | Foreach { + ($Master.split(" ").Where({$_.StartsWith("-")})) | Foreach { + If ($Master -and !$Master.StartsWith(" ")) {$Master = " $($Master)"} + If ($Slave -and !$Slave.StartsWith(" ")) {$Slave = " $($Slave)"} + If ($Master -and !$Master.EndsWith(" ")) {$Master = "$($Master) "} + If ($Slave -and !$Slave.EndsWith(" ")) {$Slave = "$($Slave) "} + $Matches = $null + $MasterCmdArg = $null If ($_ -notin $NoReplaceCmdArgs) { if ($Master -match " $_ -") { If (!($Slave -match " $_ -")) { $Slave = $Slave + " $($_)" } - } elseif ($Master -match " $_ *([^-]+)") { + # } elseif ($Master -match " $_ *([^-]+)") { + # $MasterCmdArg = $Matches[0] + # If ($Slave -match " $_ *([^-]+)") { + # $Slave = $Slave -replace " $_ *([^-]+)",$MasterCmdArg + # } else { + # $Slave = $Slave + " $($MasterCmdArg)" + # } + } elseif ($Master -match " $_ *([^( -)])+") { $MasterCmdArg = $Matches[0] - If ($Slave -match " $_ *([^-]+)") { - $Slave = $Slave -replace " $_ *([^-]+)",$MasterCmdArg + If ($Slave -match " $_ *([^( -)])+") { + $Slave = $Slave -replace " $_ *([^( -)])+",$MasterCmdArg } else { - $Slave = $Slave + " $($MasterCmdArg)" + $Slave = $Slave + " $($MasterCmdArg)" } } elseif ($Master -match " $_ *([^\s]+)") { $MasterCmdArg = $Matches[0] @@ -1449,10 +1530,10 @@ Function Merge-Command { } else { $Slave = $Slave + " $($MasterCmdArg)" } - } elseif ($Master -match " $_*([^\s]+)") { + } elseif ($Master -match " $_ ") { $MasterCmdArg = $Matches[0] - If ($Slave -match " $_*([^\s]+)") { - $Slave = $Slave -replace " $_*([^\s]+)",$MasterCmdArg + If ($Slave -match " $_ ") { + $Slave = $Slave -replace " $_ ",$MasterCmdArg } else { $Slave = $Slave + " $($MasterCmdArg)" } @@ -1468,4 +1549,166 @@ Function Merge-Command { } } +Function Invoke-ProxiedWebRequest { + $Request = $null + If ($Config.Server_Client -and $Variables.ServerRunning -and -not $ByPassServer -and -not $OutFile) { + Try { + $ProxyURi = "http://$($Config.Server_ClientIP):$($Config.Server_ClientPort)/Proxy/?url=$($Args[0])" + $Args[0] = $ProxyURi + # $Request = Invoke-WebRequest $ProxyURi -Credential $Variables.ServerClientCreds -TimeoutSec $TimeoutSec -UseBasicParsing -Headers $Headers + $Request = Invoke-WebRequest @Args -Credential $Variables.ServerClientCreds + } Catch { + # $Variables.StatusText = "Proxy Request Failed - Trying Direct: $($URi)" + } + } + if (!$Request.Content -or ($Request.StatusCode -ne 200 -and $Request.StatusCode -ne 305) -and -not $OutFile) { + Try { + $Request = Invoke-WebRequest @Args + } Catch { + # $Variables.StatusText = "Direct Request Failed: $($URi)" + } + } + + $Request + +} + +Function Get-CoinIcon { + param( + [Parameter(Mandatory = $true)] + [String]$Symbol, + [Parameter(Mandatory = $false)] + [Int]$Size=32 + ) + $Symbol = $Symbol.Trim() + If ($Symbol -like "auto *" -or $Symbol -in @("")) {Return} + # If (!$Variables.CoinIcons) { + # $Variables | Add-Member -Force @{CoinIcons = (Invoke-WebRequest "https://api.coingecko.com/api/v3/coins/list" | ConvertFrom-Json) | sort Symbol -Unique | Select Symbol,Id,Name,Image} + # } + # If (($Variables.CoinIcons | ? {$_.Symbol -eq $Symbol}) -and (($Variables.CoinIcons | ? {$_.Symbol -eq $Symbol}).image -eq $Null)) { + # ($Variables.CoinIcons | ? {$_.Symbol -eq $Symbol}).image = (Invoke-WebRequest "https://api.coingecko.com/api/v3/coins/$(($Variables.CoinIcons | ? {$_.Symbol -eq $Symbol}).id)" | ConvertFrom-Json).Image + # } + # ($Variables.CoinIcons | ? {$_.Symbol -eq $Symbol}).image.thumb + If (!$Variables.CoinIcons) { + $Variables.CoinIcons = [PSCustomObject]@{} + (Invoke-WebRequest "https://api.coingecko.com/api/v3/coins/list" | ConvertFrom-Json) | sort Symbol -Unique | ? {$_.Symbol -in $Variables.Miners.Coin} | ForEach {$Variables.CoinIcons | Add-Member -Force @{$_.Symbol = [PSCustomObject]@{id = $_.id}}} + } + If (!($Variables.CoinIcons.$Symbol.id)) { + (Invoke-WebRequest "https://api.coingecko.com/api/v3/coins/list" | ConvertFrom-Json) | sort Symbol -Unique | ? {$_.Symbol -eq $Symbol} | ForEach {$Variables.CoinIcons | Add-Member -Force @{$_.Symbol = [PSCustomObject]@{id = $_.id}}} + } + If (($Variables.CoinIcons.$Symbol.id) -and !($Variables.CoinIcons.$Symbol.Image)) { + $Variables.CoinIcons.$Symbol | Add-Member -Force @{Image = (Invoke-WebRequest "https://api.coingecko.com/api/v3/coins/$($Variables.CoinIcons.$Symbol.id)" | ConvertFrom-Json).Image.Thumb} + $Variables.CoinIcons | Convertto-Json | out-file ".\Logs\CoinIcons.json" + } + $Variables.CoinIcons.$Symbol.Image +} + +Function Load-CoinsIconsCache { + + If (Test-Path ".\Logs\CoinIcons.json") { + $Variables.CoinsIconCachePopulating = $True + $Variables.CoinIcons = (Get-Content ".\Logs\CoinIcons.json" | ConvertFrom-Json) + $Variables.CoinsIconCacheLoaded = $True + $Variables.CoinsIconCachePopulating = $False + } Else { + + # Setup runspace to load Coins icons cache in background + $IconCacheRunspace = [runspacefactory]::CreateRunspace() + $IconCacheRunspace.Open() + $IconCacheRunspace.SessionStateProxy.SetVariable("Config", $Config) + $IconCacheRunspace.SessionStateProxy.SetVariable("Variables", $Variables) + $IconCacheRunspace.SessionStateProxy.Path.SetLocation($pwd) | Out-Null + $IconCacheLoader = [PowerShell]::Create().AddScript({ + . .\Includes\include.ps1 + $Variables.CoinsIconCachePopulating = $True + + If (!$Variables.CoinsIconCacheLoaded) { + If (!$Variables.CoinIcons) { + # $Variables | Add-Member -Force @{CoinIcons = [PSCustomObject]@{}} + $CoinIcons = [PSCustomObject]@{} + (Invoke-ProxiedWebRequest "https://api.coingecko.com/api/v3/coins/list" | ConvertFrom-Json) | sort Symbol -Unique | ? {$_.Symbol -in $Variables.Miners.Coin} | ForEach {$CoinIcons | Add-Member -Force @{$_.Symbol = [PSCustomObject]@{id = $_.id}}} + } + $CoinIcons.PSobject.Properties.Name | sort | foreach { + $CoinIcons.$_ | Add-Member -Force @{Image = (Invoke-ProxiedWebRequest "https://api.coingecko.com/api/v3/coins/$($CoinIcons.$_.id)" | ConvertFrom-Json).Image.Thumb} + } + } + $Variables.CoinIcons = CoinIcons + $Variables.CoinIcons | Convertto-Json | out-file ".\Logs\CoinIcons.json" + $Variables.CoinsIconCacheLoaded = $True + $Variables.CoinsIconCachePopulating = $False + $Variables.IconCacheRunspaceHandle.Runspace.Close() + $Variables.IconCacheRunspaceHandle.Dispose() + + }) + + $IconCacheLoader.Runspace = $IconCacheRunspace + $Variables.IconCacheRunspaceHandle = $IconCacheLoader.BeginInvoke() + } +} + +Function Get-PoolIcon { + param( + [Parameter(Mandatory = $true)] + [String]$Pool, + [Parameter(Mandatory = $false)] + [Int]$Size=32 + ) + + If (!$Variables.poolapiref -and (Test-Path ".\Config\poolapiref.json")){ + $Variables.poolapiref = Get-Content ".\Config\poolapiref.json" | ConvertFrom-Json + } + ($Variables.poolapiref | ? {$_.Name -eq $Pool}).IconURi + +} + +Function Get-DisplayCurrency { + param( + [Parameter(Mandatory = $false)] + [Decimal]$Value, + [Parameter(Mandatory = $false)] + [Decimal]$Factor=1 + ) + + $Result = [PSCustomObject]@{ + Currency = If($Config.Passwordcurrency -eq "BTC") {"$([char]0x20BF)"} Else {$Config.Passwordcurrency} + Value = $Value * $Factor + RoundedValue = [Math]::Round($This.Value, 3) + Unit = "" + UnitString = "$($This.Unit)$($This.Currency)" + UnitStringPerDay = "$($This.Unit)$($This.Currency)/Day" + DisplayString = "$($This.RoundedValue) $($This.Unit)$($This.Currency)" + DisplayStringPerDay = "$($This.RoundedValue) $($This.Unit)$($This.Currency)/Day" + } + $Result | Add-Member -Force -MemberType ScriptProperty -Name 'RoundedValue' -Value{ [Math]::Round($This.Value, 3) } + $Result | Add-Member -Force -MemberType ScriptProperty -Name 'UnitString' -Value{ "$($This.Unit)$($This.Currency)" } + $Result | Add-Member -Force -MemberType ScriptProperty -Name 'UnitStringPerDay' -Value{ "$($This.Unit)$($This.Currency)/Day" } + $Result | Add-Member -Force -MemberType ScriptProperty -Name 'DisplayString' -Value{ "$($This.RoundedValue) $($This.Unit)$($This.Currency)" } + $Result | Add-Member -Force -MemberType ScriptProperty -Name 'DisplayStringPerDay' -Value{ "$($This.RoundedValue) $($This.Unit)$($This.Currency)/Day" } + + $Result.Unit = Switch ([Math]::Floor($Result.Value)) { + {$_ -le 0} {"m";$Result.Value*=1000;Break} + {$_ -le 999} {"";Break} + {$_ -le 999999} {"K";$Result.Value/=1000;Break} + {$_ -le 999999999} {"M";$Result.Value/=1000000;Break} + {$_ -le 999999999999} {"G";$Result.Value/=1000000000;Break} + } + # $Result.Value = [Math]::Round($Result.Value, 3) + $Result +} + +Function ConvertTo-ImagePath { + param( + [Parameter(Mandatory = $true)] + [String]$Text + ) + + $ImagePath = Switch ($Text) { + "CPU" {"https://img.icons8.com/metro/26/000000/electronics.png"} + "NVIDIA" {"https://www.nvidia.com/favicon.ico"} + "AMD" {"https://www.amd.com/themes/custom/amd/favicon.ico"} + "Top" {"https://img.icons8.com/metro/32/000000/double-up.png"} + "Paused" {"https://img.icons8.com/flat_round/64/000000/pause--v1.png"} + } + $ImagePath +} \ No newline at end of file diff --git a/Includes/MinerCustomConfig.ps1 b/Includes/MinerCustomConfig.ps1 index b74fe37..040689a 100644 --- a/Includes/MinerCustomConfig.ps1 +++ b/Includes/MinerCustomConfig.ps1 @@ -23,10 +23,13 @@ version: 5.9.9 version date: 20191108 #> - $MinerCustomConfig = $MinerCustomConfig | ? {$_.Enabled} + $AbortCurrentPool = $False + $DontUseCustom = $False + $MinerCustomConfig = $MinerCustomConfig.Where({$_.Enabled}) $Combinations = $MinerCustomConfig | group algo,Pool,miner,coin $CustomCommands = [PSCustomObject]@{} $DontUseCustom = $False + $DevCommand = "" $WinningCustomConfig = $null $CurrentCombination = $null If ($Pool.Algorithm) {$CustomCommands | Add-Member -Force @{($Pool.Algorithm) = $Commands.($Algo)}} @@ -40,7 +43,9 @@ version date: 20191108 #Apply user Args # Test custom config for Algo, coin, Miner, Coin #PrioritizedCombinations | highest at bottom - @( + + $OrderedCombinations = @( + ", , $($Name), ", "$($Pool.Algorithm), , , ", "$($Pool.Algorithm), $($Pool.Name), , ", "$($Pool.Algorithm), , $($Name), ", @@ -48,32 +53,28 @@ version date: 20191108 "$($Pool.Algorithm), $($Pool.Name), , $($Pool.Coin)" "$($Pool.Algorithm), , , $($Pool.Coin)" "$($Pool.Algorithm), $($Pool.Name), $($Name), $($Pool.Coin)" - ) | foreach { - if ($_ -in $Combinations.name) { - $CurrentCombination = $_ - $WinningCustomConfig = ($Combinations | ? {$_.name -eq $CurrentCombination}).group[0] - - If ($WinningCustomConfig.code) { - $WinningCustomConfig.code | Invoke-Expression - # Can't get return or continue to work in context correctly when inserted in custom code. - # Workaround with variable. So users have a way to not apply custom config based on conditions. - If ($DontUseCustom) {Return} - } - If ($WinningCustomConfig.CustomPasswordAdds) { - $CustomPasswordAdds = $WinningCustomConfig.CustomPasswordAdds.Trim() - $Password = Merge-Command -Slave $Password -Master $CustomPasswordAdds -Type "Password" - } - If ($WinningCustomConfig.CustomCommandAdds) { - $CustomCmdAdds = $WinningCustomConfig.CustomCommandAdds.Trim() - $CustomCmdAdds = Merge-Command -Slave $DevCommand -Master $CustomCmdAdds -Type "Command" - } + ) + $WinningCustomConfig = ($Combinations.Where({$_.Name -like (Compare-Object $OrderedCombinations $Combinations.Name -IncludeEqual -ExcludeDifferent -PassThru | select -Last 1)})).Group + if ($WinningCustomConfig) { + If ($WinningCustomConfig.IncludeCoins -and $Pool.Coin -notin $WinningCustomConfig.IncludeCoins) {$AbortCurrentPool = $true ; Return} + If ($WinningCustomConfig.ExcludeCoins -and $Pool.Coin -in $WinningCustomConfig.ExcludeCoins) {$AbortCurrentPool = $true ; Return} + If ($WinningCustomConfig.code) { + $WinningCustomConfig.code | Invoke-Expression + # Can't get return or continue to work in context correctly when inserted in custom code. + # Workaround with variable. So users have a way to not apply custom config based on conditions. + If ($DontUseCustom) {Return} + } + If ($WinningCustomConfig.CustomPasswordAdds -and !($Variables.DonationStart -or $Variables.DonationRunning)) { + $CustomPasswordAdds = $WinningCustomConfig.CustomPasswordAdds.Trim() + $Password = Merge-Command -Slave $Password -Master $CustomPasswordAdds -Type "Password" + } + If ($WinningCustomConfig.CustomCommandAdds) { + $CustomCmdAdds = $WinningCustomConfig.CustomCommandAdds.Trim() + $CustomCmdAdds = Merge-Command -Slave $DevCommand -Master $CustomCmdAdds -Type "Command" } - $CustomPasswordAdds = $null - $CustomCommandAdds = $null } + $CustomPasswordAdds = $null - If ($WinningCustomConfig.IncludeCoins -and $Pool.Coin -notin $WinningCustomConfig.IncludeCoins) {$AbortCurrentPool = $true ; return} - If ($WinningCustomConfig.ExcludeCoins -and $Pool.Coin -in $WinningCustomConfig.ExcludeCoins) {$AbortCurrentPool = $true ; return} $WinningCustomConfig = $null diff --git a/Includes/MinerCustomConfigEditor.ps1 b/Includes/MinerCustomConfigEditor.ps1 index c9333bb..2c03e3d 100644 --- a/Includes/MinerCustomConfigEditor.ps1 +++ b/Includes/MinerCustomConfigEditor.ps1 @@ -103,7 +103,7 @@ $HelpLabel_OpenLink= } #---------------------------------------------- #region Generated Form Code -$form1.Text = "NPlusMiner Custom Miners Confiuration Editor" +$form1.Text = "NPlusMiner Custom Miners Configuration Editor" $NPMIcon = New-Object system.drawing.icon (".\Includes\NPM.ICO") $form1.Icon = $NPMIcon $form1.Name = "form1" @@ -476,4 +476,4 @@ $form1.ShowDialog()| Out-Null } #End Function #Call the Function -GenerateForm \ No newline at end of file +GenerateForm diff --git a/Includes/Server.ps1 b/Includes/Server.ps1 new file mode 100644 index 0000000..61e891a --- /dev/null +++ b/Includes/Server.ps1 @@ -0,0 +1,872 @@ +<# +This file is part of NPlusMiner +Copyright (c) 2018 Nemo +Copyright (c) 2018-2020 MrPlus + +NPlusMiner is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +NPlusMiner is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +#> + +<# +Product: NPlusMiner +File: Server.ps1 +version: 6.2.0 +version date: 20201208 +#> + + +function Test-ServerRules { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [Int]$Port, + [Parameter(Mandatory = $false)] + [string]$Type = "all" # all, urlacl, firewall, firewall-tcp, firewall-udp + ) + $ServerRulesStatus = $true + if ($ServerRulesStatus -and ($Type -eq "firewall" -or $Type -eq "firewall-tcp" -or $Type -eq "all")) { + $RuleName = "NPlusMiner Server $($Port) TCP" + $RuleACLs = & netsh advfirewall firewall show rule name="$($RuleName)" | Out-String + if (-not $RuleACLs.Contains($RuleName)) {$ServerRulesStatus = $false} + } + if ($ServerRulesStatus -and ($Type -eq "firewall" -or $Type -eq "firewall-udp" -or $Type -eq "all")) { + $RuleName = "NPlusMiner Server $($Port) UDP" + $RuleACLs = & netsh advfirewall firewall show rule name="$($RuleName)" | Out-String + if (-not $RuleACLs.Contains($RuleName)) {$ServerRulesStatus = $false} + } + if ($ServerRulesStatus -and ($Type -eq "urlacl" -or $Type -eq "all")) { + $urlACLs = & netsh http show urlacl | Out-String + if (-not $urlACLs.Contains("http://+:$($Port)/")) {$ServerRulesStatus = $false} + } + $ServerRulesStatus +} + +function Initialize-ServerRules { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [Int]$Port + ) + + if (-not (Test-ServerRules -Port $Port -Type "urlacl")) { + # S-1-5-32-545 = SID for Users group + (Start-Process netsh -Verb runas -PassThru -ArgumentList "http add urlacl url=http://+:$($Port)/ sddl=D:(A;;GX;;;S-1-5-32-545) user=everyone").WaitForExit(5000)>$null + } + + if (-not (Test-ServerRules -Port $Port -Type "firewall-tcp")) { + (Start-Process netsh -Verb runas -PassThru -ArgumentList "advfirewall firewall add rule name=`"NPlusMiner Server $($Port) TCP`" dir=in action=allow protocol=TCP localport=$($Port)").WaitForExit(5000)>$null + } + + if (-not (Test-ServerRules -Port $Port -Type "firewall-udp")) { + (Start-Process netsh -Verb runas -PassThru -ArgumentList "advfirewall firewall add rule name=`"NPlusMiner Server $($Port) UDP`" dir=in action=allow protocol=UDP localport=$($Port)").WaitForExit(5000)>$null + } +} + +function Reset-ServerRules { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [Int]$Port + ) + + if (Test-ServerRules -Port $Port -Type "urlacl") { + (Start-Process netsh -Verb runas -PassThru -ArgumentList "http delete urlacl url=http://+:$($Port)/").WaitForExit(5000)>$null + } + + if (Test-ServerRules -Port $Port -Type "firewall") { + (Start-Process netsh -Verb runas -PassThru -ArgumentList "advfirewall firewall delete rule name=`"NPlusMiner Server $($Port) TCP`"").WaitForExit(5000)>$null + (Start-Process netsh -Verb runas -PassThru -ArgumentList "advfirewall firewall delete rule name=`"NPlusMiner Server $($Port) UDP`"").WaitForExit(5000)>$null + } +} + +Function Start-Server { + if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + Initialize-ServerRules $Config.Server_Port + + # Setup runspace to launch the API webserver in a separate thread + $ServerRunspace = [runspacefactory]::CreateRunspace() + $ServerRunspace.Open() + $ServerRunspace.SessionStateProxy.SetVariable("Config", $Config) + $ServerRunspace.SessionStateProxy.SetVariable("Variables", $Variables) + $ServerRunspace.SessionStateProxy.Path.SetLocation($pwd) | Out-Null + + $Server = [PowerShell]::Create().AddScript({ + . .\Includes\include.ps1 + + Load-CoinsIconsCache + + Function Get-StringHash([String] $String,$HashName = "MD5") + { + $StringBuilder = New-Object System.Text.StringBuilder + [System.Security.Cryptography.HashAlgorithm]::Create($HashName).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String))|%{ + [Void]$StringBuilder.Append($_.ToString("x2")) + } + $StringBuilder.ToString() + } + [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" + + $BasePath = "$($pwd)" + if ($MyInvocation.MyCommand.Path) { + Set-Location (Split-Path $MyInvocation.MyCommand.Path) + $BasePath = $MyInvocation.MyCommand.Path + } + + $MIMETypes = @{ + ".js" = "application/x-javascript" + ".html" = "text/html" + ".htm" = "text/html" + ".json" = "application/json;charset=UTF-8" + ".css" = "text/css" + ".txt" = "text/plain" + ".ico" = "image/x-icon" + ".ps1" = "text/html" # ps1 files get executed, assume their response is html + ".png" = "image/png" + } + + # Load Branding + If (Test-Path ".\Config\Branding.json") { + $Branding = Get-Content ".\Config\Branding.json" | ConvertFrom-Json + } Else { + $Branding = [PSCustomObject]@{ + LogoPath = "http://tiny.cc/yvlaoz" + BrandName = "NPlusMiner" + BrandWebSite = "https://github.com/MrPlusGH/NPlusMiner" + ProductLable = "NPlusMiner" + } + } + + If (Test-Path ".\Logs\Server.log") {Remove-Item ".\Logs\Server.log" -Force} + Start-Transcript ".\Logs\ServerTR.log" + # $pid | out-host + if ([Net.ServicePointManager]::SecurityProtocol -notmatch [Net.SecurityProtocolType]::Tls12) { + [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12 + } + + [System.Collections.ArrayList]$ProxyCache = @() + + [System.Collections.ArrayList]$Clients = @() + + $ServerListener = New-Object Net.HttpListener + $ServerListener.Prefixes.Add("http://+:$($Config.Server_Port)/") + $ServerListener.AuthenticationSchemes = [System.Net.AuthenticationSchemes]::Basic + + + + #ignore self-signed/invalid ssl certs + # Breaks TLS all up ! + # [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$True} + + Foreach ($P in $Up) {$Hso.Prefixes.Add($P)} + $ServerListener.Start() + While ($ServerListener.IsListening -and -not $Variables.StopServer) { + if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + # $HC = $ServerListener.GetContext() + + $contextTask = $ServerListener.GetContextAsync() + while (-not $contextTask.AsyncWaitHandle.WaitOne(500)) { } + $HC = $contextTask.GetAwaiter().GetResult() + + $HReq = $HC.Request + # $Hreq | Out-Host + # $Hreq | convertto-json -Depth 10 | Out-File ".\Logs\HReq.json" + $Path = $Hreq.Url.LocalPath + $ClientAddress = $Hreq.RemoteEndPoint.Address.ToString() + $ClientPort = $Hreq.RemoteEndPoint.Port + $HRes = $HC.Response + # $HRes.Headers.Add("Content-Type","text/html") + + If (($Clients.Where({$_.Address -eq $ClientAddress})).count -lt 1) { + $Clients.Add([PSCustomObject]@{ + Address = $ClientAddress + }) + } + # $Hreq.RemoteEndPoint | Out-host + # $ProxURL | Out-Host + + # If ("Proxy-Connection" -in $HReq.Headers -and $ProxURL) { + # If ($ProxURL) { + if((-not $HC.User.Identity.IsAuthenticated -or $HC.User.Identity.Name -ne $Config.Server_User -or $HC.User.Identity.Password -ne $Config.Server_Password)) { + $Data = "Access denied" + $StatusCode = [System.Net.HttpStatusCode]::Forbidden + $ContentType = "text/html" + $AuthSuccess = $False + } else { + +# Define Page Header + $Header = +@" + + +
+ + Copyright (c) 2018-$((Get-Date).year) MrPlus +
+ + $(Get-Date)     $($Branding.ProductLable) $($Variables.CurrentVersion)     Runtime $(("{0:dd\ \d\a\y\s\ hh\:mm}" -f ((get-date)-$Variables.ScriptStartDate)))     Path: $($BasePath)     API Cache hit ratio: $("{0:N0}" -f $CacheHitsRatio)%
+ Worker Name: $($Config.WorkerName) +     Average Profit: $(((Get-DisplayCurrency ($Variables.Earnings.Values | measure -Property Growth24 -Sum).sum)).DisplayStringPerDay) +     $(If($Variables.Rates.($Config.Currency) -gt 0){"$($Config.Passwordcurrency)/$($Config.Currency) $($Variables.Rates.($Config.Currency).ToString("N2"))"}) +
+
+ Running Miners     Benchmarks     Switching log +"@ + + If ($Variables.Paused) { + $Header += "      Start Mining" + } Else { + $Header += "      Pause Mining" + } + $Header += +@" + + Discord +
+"@ + + # + # + + If (Test-Path ".\Config\Peers.json") { + $Header += "Rigs:     " + (get-content ".\Config\Peers.json" | ConvertFrom-Json) | Sort Name | foreach { + $Peer = $_ + $Header += "$($Peer.Name)     " + } + $Header += "
" + } + +# Define Page Footer + $Footer = +@" +
+
+ Copyright (c) 2018-2020 MrPlus + + icons8.com +
+
+"@ + + + $AuthSuccess = $True + $HReq.RawUrl | write-Host + Switch($Path) { + "/StopServer" { + write-host "Stop Requested" + $Variables.StopServer = $True + $Variables.ServerRunning = $False + $Hso.Close() + $ContentType = "text/html" + $Content = "OK" + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/proxy/" { + $ProxyCache = $ProxyCache.Where({$_.Date -ge (Get-Date).AddMinutes(-$Config.Server_ServerProxyTimeOut)}) + $ProxURL = $HReq.RawUrl.Replace("/Proxy/?url=","") + # $ProxURL = $HReq.QueryString['URL'] + $ProxURLHash = Get-StringHash $ProxURL + + If (($ProxyCache.Where({$_.ID -eq $ProxURLHash -and $_.date -ge (Get-Date).AddMinutes(-$Config.Server_ServerProxyTimeOut)})).Content -ne $null) { + # "Get cache content" | Out-Host + $CacheHits++ + $Content = ($ProxyCache.Where({$_.ID -eq $ProxURLHash})).Content + $StatusCode = [System.Net.HttpStatusCode]::UseProxy + } else { + # "Web Query" | Out-Host + $WebHits++ + $Wco = New-Object Net.Webclient + $Content = $Wco.downloadString("$ProxURL") + If ($Content) { + $ProxyCache = $ProxyCache.Where({$_.ID -ne $ProxURLHash}) + $ProxyCache.Add([PSCustomObject]@{ + ID = $ProxURLHash + URL = $ProxURL + Date = Get-Date + Content = $Content + }) + } + $StatusCode = [System.Net.HttpStatusCode]::OK + $Wco.Dispose() + } + + If (($CacheHits + $WebHits)) {$CacheHitsRatio = $CacheHits / ($CacheHits + $WebHits) * 100} + Break + } + "/RegisterRig/" { + $ContentType = "text/html" + + $Peers = @() + $PeerUpdate = $False + + $RegisterRigName = $HReq.QueryString['Name'] + $RegisterRigIP = $HReq.QueryString['IP'] + $RegisterRigPort = $HReq.QueryString['Port'] + $RegisterBackRegistrationNotAllowed = If ($HReq.QueryString['BackRegistrationNotAllowed'] -eq "true") {$True} Else {$False} + + If (!$RegisterRigName -or (!$RegisterRigIP -and !$ClientAddress) -or !$RegisterRigPort) { + $StatusCode = 404 + $Content = "Incomplete registration" + } Else { + $Peer = [PSCustomObject]@{ + Name = $RegisterRigName + IP = If (!$RegisterRigIP) {$ClientAddress} Else {$RegisterRigIP} + Port = $RegisterRigPort + } + If (Test-Path ".\Config\Peers.json") { + $Peers = Get-Content ".\Config\Peers.json" | convertfrom-json + If (@($Peers).count -eq 1) { $Peers = @($Peers) } + } + + If (($Peers | ? {$_.Name -eq $RegisterRigName}) -and !(compare $Peer $Peers -IncludeEqual -ExcludeDifferent) -and !($Peers | ? {$_.Name -eq $RegisterRigName}).PreventUpdates) { + ($Peers | ? {$_.Name -eq $RegisterRigName}).IP = $Peer.IP + ($Peers | ? {$_.Name -eq $RegisterRigName}).Port = $Peer.Port + $PeerUpdate = $True + } elseif (!($Peers | ? {$_.Name -eq $RegisterRigName})) { + $Peers += $Peer + $PeerUpdate = $True + } + + $PeerPing = + If ( $Peer.Name -eq $Config.WorkerName ) { + $True + } Else { + Try { (Invoke-WebRequest "http://$($Peer.IP):$($Peer.Port)/ping" -Credential $Variables.ServerClientCreds -TimeoutSec 3).content -eq "Server Alive" } Catch {$False} + } + + If ($PeerUpdate -and $PeerPing) { + $Peers | convertto-json | out-file ".\Config\Peers.json" + If ($RegisterRigName -ne $Config.WorkerName -and !$RegisterBackRegistrationNotAllowed){ + # Back registration won't work until we switch the listener to ASync + # Try { (Invoke-WebRequest "http://$($Peer.IP):$($Peer.Port)/RegisterRig/?Name=$($Config.WorkerName)&Port=$($Config.Server_Port)&RegisterBackRegistrationNotAllowed=true" -Credential $Variables.ServerClientCreds -TimeoutSec 3).content -eq "Server Alive" } Catch {$False} + } + + $Content = "$($Peer.Name)`n$($Peer.IP)`n$($Peer.Port)" + $StatusCode = [System.Net.HttpStatusCode]::OK + } Else { + $StatusCode = 404 + $Content = "Peer not responding" + } + } + $Peers = $Null + Break + } + "/ping" { + $ContentType = "text/html" + $Content = "Server Alive" + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/ClearCache" { + $ContentType = "text/html" + $CacheHits = 0 + $WebHits = 0 + rv ProxyCache + [System.Collections.ArrayList]$ProxyCache = @() + $Content = "OK" + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/ExportCache" { + $ContentType = "text/html" + $ProxyCache | convertto-json | Out-File ".\logs\ProxyCache.json" + $Content = "OK" + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/Config.json" { + $Title = "Config.json" + # $Content = ConvertTo-Html -CssUri "file:///d:/Nplusminer/Includes/Web.css " -Title $Title -Body "

$Title

`n
Updated: on $(Get-Date)
" + $ContentType = $MIMETypes[".json"] + $Content = $Config | ConvertTo-Json -Depth 10 + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/Variables.json" { + $Title = "Variables.json" + # $Content = ConvertTo-Html -CssUri "file:///d:/Nplusminer/Includes/Web.css " -Title $Title -Body "

$Title

`n
Updated: on $(Get-Date)
" + $ContentType = $MIMETypes[".json"] + $Content = $Variables | ConvertTo-Json -Depth 10 + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/Earnings.json" { + $Title = "Earnings.json" + # $Content = ConvertTo-Html -CssUri "file:///d:/Nplusminer/Includes/Web.css " -Title $Title -Body "

$Title

`n
Updated: on $(Get-Date)
" + $ContentType = $MIMETypes[".json"] + $Content = $Variables.Earnings | ConvertTo-Json -Depth 10 + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/RunningMiners.json" { + $Title = "RunningMiners.json" + # $Content = ConvertTo-Html -CssUri "file:///d:/Nplusminer/Includes/Web.css " -Title $Title -Body "

$Title

`n
Updated: on $(Get-Date)
" + $ContentType = $MIMETypes[".json"] + $Content = $Variables.ActiveMinerPrograms.Clone().Where( {$_.Status -eq "Running"} ) | Sort Type | ConvertTo-Json -Depth 10 + If ($Variables.Paused) { + $Content = [PSCustomObject]@{ + Type = "Paused" + } | ConvertTo-Json + } + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/Benchmarks.json" { + $Title = "Benchmarks.json" + # $Content = ConvertTo-Html -CssUri "file:///d:/Nplusminer/Includes/Web.css " -Title $Title -Body "

$Title

`n
Updated: on $(Get-Date)
" + $ContentType = $MIMETypes[".json"] + $Content = $Variables.Miners | ConvertTo-Json -Depth 10 + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/Peers.json" { + $Title = "Peers.json" + # $Content = ConvertTo-Html -CssUri "file:///d:/Nplusminer/Includes/Web.css " -Title $Title -Body "

$Title

`n
Updated: on $(Get-Date)
" + $ContentType = $MIMETypes[".json"] + If (Test-Path ".\Config\Peers.json") {$Content = Get-Content ".\Config\Peers.json" -Raw} + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/Status" { + $Title = "Status" + # $Content = ConvertTo-Html -CssUri "file:///d:/Nplusminer/Includes/Web.css " -Title $Title -Body "

$Title

`n
Updated: on $(Get-Date)
" + $ContentType = $MIMETypes[".html"] + + $EarningsTrends = [PSCustomObject]@{} + $TrendSign = switch ([Math]::Round((($Variables.Earnings.Values | measure -Property Growth1 -Sum).sum*1000*24),3) - [Math]::Round((($Variables.Earnings.Values | measure -Property Growth6 -Sum).sum*1000*4),3)) { + {$_ -eq 0} + {"  "} + {$_ -gt 0} + {"  "} + {$_ -lt 0} + {"  "} + } + $EarningsTrends | Add-Member -Force @{"Last 1h $TrendSign" = ((Get-DisplayCurrency ($Variables.Earnings.Values | measure -Property Growth1 -Sum).sum 24)).DisplayStringPerDay} + $TrendSign = switch ([Math]::Round((($Variables.Earnings.Values | measure -Property Growth6 -Sum).sum*1000*4),3) - [Math]::Round((($Variables.Earnings.Values | measure -Property Growth24 -Sum).sum*1000),3)) { + {$_ -eq 0} + {"  "} + {$_ -gt 0} + {"  "} + {$_ -lt 0} + {"  "} + } + $EarningsTrends | Add-Member -Force @{"Last 6h $TrendSign" = ((Get-DisplayCurrency ($Variables.Earnings.Values | measure -Property Growth6 -Sum).sum 4)).DisplayStringPerDay} + $TrendSign = switch ([Math]::Round((($Variables.Earnings.Values | measure -Property Growth24 -Sum).sum*1000),3) - [Math]::Round((($Variables.Earnings.Values | measure -Property BTCD -Sum).sum*1000*0.96),3)) { + {$_ -eq 0} + {"  "} + {$_ -gt 0} + {"  "} + {$_ -lt 0} + {"  "} + } + $EarningsTrends | Add-Member -Force @{"Last 24h $TrendSign" = ((Get-DisplayCurrency ($Variables.Earnings.Values | measure -Property Growth24 -Sum).sum)).DisplayStringPerDay} + $Header += $EarningsTrends | ConvertTo-Html -CssUri "./Includes/Web.css" + + If (Test-Path ".\logs\DailyEarnings.csv"){ + $Chart1 = Invoke-Expression -Command ".\Includes\Charting.ps1 -Chart 'Front7DaysEarnings' -Width 505 -Height 85 -Currency $($Config.Passwordcurrency)" + $Chart2 = Invoke-Expression -Command ".\Includes\Charting.ps1 -Chart 'DayPoolSplit' -Width 200 -Height 85 -Currency $($Config.Passwordcurrency)" + + + $Header += +@" +
+ + Earnings Tracker + +
+ + + + + + + + + + + + +
Past 7 days earningsPer pool earnings
+ + + +

+
+"@ + } + + If ($Variables.Earnings -and $Config.TrackEarnings) { + $DisplayEarnings = [System.Collections.ArrayList]@($Variables.Earnings.Values | select @( + @{Name="Pool";Expression={"  " +$_.Pool}}, + @{Name="Trust";Expression={"{0:P0}" -f $_.TrustLevel}}, + @{Name="Balance";Expression={$_.Balance}}, + # @{Name="Unpaid";Expression={$_.total_unpaid}}, + # @{Name="BTC/D";Expression={"{0:N8}" -f ($_.BTCD)}}, + @{Name="1h $((Get-DisplayCurrency $_.Growth1 24).UnitStringPerDay)";Expression={(Get-DisplayCurrency $_.Growth1 24).RoundedValue}}, + @{Name="6h $((Get-DisplayCurrency $_.Growth6 4).UnitStringPerDay)";Expression={(Get-DisplayCurrency $_.Growth6 4).RoundedValue}}, + @{Name="24h $((Get-DisplayCurrency $_.Growth24).UnitStringPerDay)";Expression={(Get-DisplayCurrency $_.Growth24).RoundedValue}}, + + @{Name = "Est. Pay Date"; Expression = {if ($_.EstimatedPayDate -is 'DateTime') {$_.EstimatedPayDate.ToShortDateString()} else {$_.EstimatedPayDate}}}, + + @{Name="PaymentThreshold";Expression={"$($_.PaymentThreshold) ($('{0:P0}' -f $($_.Balance / $_.PaymentThreshold)))"}}#, + # @{Name="Wallet";Expression={$_.Wallet}} + ) | Sort "1h $((Get-DisplayCurrency $_.Growth1 24).UnitStringPerDay)","6h $((Get-DisplayCurrency $_.Growth6 4).UnitStringPerDay)","24h $((Get-DisplayCurrency $_.Growth24).UnitStringPerDay)" -Descending) + $DisplayEarnings = [System.Collections.ArrayList]@($DisplayEarnings) | ConvertTo-Html -CssUri "./Includes/Web.css" -Title $Title -PreContent $Header + $Content = [System.Web.HttpUtility]::HtmlDecode($DisplayEarnings) + } + $Content += +@" +
+ + Running Miners + +
+"@ + + If (Test-Path ".\Config\Peers.json") { + $Peers = Get-Content ".\Config\Peers.json" | ConvertFrom-Json + } Else { + $Peers = @([PSCustomObject]@{ Name = $Config.WorkerName ; IP = "127.0.0.1" ; Port = $Config.Server_Port }) + } + $Miners = @() + $Peers | foreach { + $Peer = $_ + If ($Peer.Name -eq $Config.WorkerName) { + $Miners += $Variables.ActiveMinerPrograms.Clone().Where( {$_.Status -eq "Running"} ) | select @{Name = "Rig";Expression={$Peer.Name}},* + } else { + $Miners += (Invoke-WebRequest "http://$($Peer.IP):$($Peer.Port)/RunningMiners.json" -Credential $Variables.ServerCreds -TimeoutSec 5 | ConvertFrom-Json) | select @{Name = "Rig";Expression={$Peer.Name}},* + } + } + $MinersTable = [System.Collections.ArrayList]@($Miners | select @( + @{Name = "Rig";Expression={$_.Rig}}, + @{Name = "Type";Expression={$_.Type}}, + @{Name = "Algorithm";Expression={$_.Algorithms}}, + # @{Name = "Coin"; Expression={"###CoinIcon###$($_.Coin.ToLower())###IconSize###" + $_.Coin}}, + # @{Name = "Coin"; Expression={If($_.Coin -and $_.Coin -ne ""){"  " + $_.Coin}else{""}}}, + @{Name = "Coin"; Expression={$_.Pools.PSObject.Properties.Value | ForEach { + Try { + "  " + $_.Coin + } Catch {""} + }}}, + @{Name = "Miner";Expression={$_.Name}}, + @{Name = "HashRate";Expression={"$($_.HashRate | ConvertTo-Hash)/s"}}, + @{Name = "Active";Expression={"{0:hh}:{0:mm}:{0:ss}" -f [TimeSpan]$_.Active.Ticks}}, + @{Name = "Total Active";Expression={"{0:hh}:{0:mm}:{0:ss}" -f [TimeSpan]$_.TotalActive.Ticks}}, + # @{Name = "Pool"; Expression={$_.Pools.PSObject.Properties.Value | ForEach {"$($_.Name)"}}} ) | sort Rig,Type + @{Name = "Pool"; Expression={$_.Pools.PSObject.Properties.Value | ForEach {"  " + $_.Name}}} ) | sort Rig,Type + ) | ConvertTo-Html -CssUri "./Includes/Web.css" + # $MinersTable = $MinersTable -Replace "###CoinIcon###", "  " + + $MinersTable = [System.Web.HttpUtility]::HtmlDecode($MinersTable) + + # $MinersTable = [regex]::Replace($MinersTable,'###CoinIcon###(.*)###IconSize###',{param($match) "  "}) + + $Content += $MinersTable + + ForEach ($Type in ($Miners.Type | Sort -Unique)) { + $Content = $Content -Replace "$($Type)", "  $($Type)" + } + + $Content += $Footer + $Content = [System.Web.HttpUtility]::HtmlDecode($Content) + + # $Content = $Header + $Content + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/RunningMiners" { + $Title = "Running Miners" + # $Content = ConvertTo-Html -CssUri "file:///d:/Nplusminer/Includes/Web.css " -Title $Title -Body "

$Title

`n
Updated: on $(Get-Date)
" + $ContentType = $MIMETypes[".html"] + $Content = [System.Collections.ArrayList]@($Variables.ActiveMinerPrograms.Clone() | ? {$_.Status -eq "Running"} | select @( + @{Name = "Type";Expression={$_.Type}}, + @{Name = "Algorithm";Expression={$_.Algorithms}}, + @{Name = "Coin"; Expression={$_.Coin}}, + @{Name = "Miner";Expression={$_.Name}}, + @{Name = "HashRate";Expression={"$($_.HashRate | ConvertTo-Hash)/s"}}, + @{Name = "Active";Expression={"{0:hh}:{0:mm}:{0:ss}" -f $_.Active}}, + @{Name = "Total Active";Expression={"{0:hh}:{0:mm}:{0:ss}" -f $_.TotalActive}}, + # @{Name = "Host";Expression={$_.Host}}, + @{Name = "Pool"; Expression={$_.Pools.PSObject.Properties.Value | ForEach {"$($_.Name)"}}} + ) | sort Type + ) | ConvertTo-Html -CssUri "./Includes/Web.css" -Title $Title -PreContent $Header + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/PeersRunningMiners" { + $Title = "Peers Running Miners" + # $Content = ConvertTo-Html -CssUri "file:///d:/Nplusminer/Includes/Web.css " -Title $Title -Body "

$Title

`n
Updated: on $(Get-Date)
" + $ContentType = $MIMETypes[".html"] + + If (Test-Path ".\Config\Peers.json") { + $Peers = Get-Content ".\Config\Peers.json" | ConvertFrom-Json + } Else { + $Peers = @([PSCustomObject]@{ Name = $Config.WorkerName ; IP = "127.0.0.1" ; Port = $Config.Server_Port }) + } + $Miners = @() + + $Peers | foreach { + $Peer = $_ + If ($Peer.Name -eq $Config.WorkerName) { + $Miners += $Variables.ActiveMinerPrograms.Clone() | ? {$_.Status -eq "Running"} | select @{Name = "Rig";Expression={$Peer.Name}},* + } else { + $Miners += (Invoke-WebRequest "http://$($Peer.IP):$($Peer.Port)/RunningMiners.json" -Credential $Variables.ServerCreds | ConvertFrom-Json) | select @{Name = "Rig";Expression={$Peer.Name}},* + } + } + $MinersTable = [System.Collections.ArrayList]@($Miners | select @( + @{Name = "Rig";Expression={$_.Rig}}, + @{Name = "Type";Expression={$_.Type}}, + @{Name = "Algorithm";Expression={$_.Algorithms}}, + @{Name = "Coin"; Expression={"###CoinIcon###$($_.Coin.ToLower())###IconSize###" + $_.Coin}}, + @{Name = "Miner";Expression={$_.Name}}, + @{Name = "HashRate";Expression={"$($_.HashRate | ConvertTo-Hash)/s"}}, + @{Name = "Active";Expression={"{0:hh}:{0:mm}:{0:ss}" -f [TimeSpan]$_.Active.Ticks}}, + @{Name = "Total Active";Expression={"{0:hh}:{0:mm}:{0:ss}" -f [TimeSpan]$_.TotalActive.Ticks}}, + @{Name = "Pool"; Expression={$_.Pools.PSObject.Properties.Value | ForEach {"$($_.Name)"}}} ) | sort Rig,Type + ) | ConvertTo-Html -CssUri "./Includes/Web.css" -Title $Title -PreContent $Header + $MinersTable = $MinersTable -Replace "###CoinIcon###", "  " + $Content = $MinersTable + # $Content = $Header + $Content + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/Benchmarks" { + $Title = "Benchmarks" + # $Content = ConvertTo-Html -CssUri "file:///d:/Nplusminer/Includes/Web.css " -Title $Title -Body "

$Title

`n
Updated: on $(Get-Date)
" + If (!$Variables.CoinsIconCacheLoaded -and !$Variables.CoinsIconCachePopulating) {Load-CoinsIconsCache} + + $ContentType = $MIMETypes[".html"] + $Content = $Header + $Content += "
" + + $Miners = $Variables["Miners"].Clone() + + ForEach ($Type in ($Miners.Type | Sort -Unique -Descending)) { + $Content += "  $($Type)    " + } + + ForEach ($Type in ($Miners.Type | Sort -Unique -Descending)) { + $Content += +@" +
+
+ +    $($Type)
+
+
+"@ + + $DisplayEstimations = [System.Collections.ArrayList]@($Miners.Where( {$_.Type -eq $Type} ) | sort $_.Profits.PSObject.Properties.Value -Descending | Select @( + @{Name = "Type";Expression={$_.Type}}, + @{Name = "Miner";Expression={$_.Name}}, + @{Name = "Algorithm";Expression={$_.HashRates.PSObject.Properties.Name}}, + # @{Name = "Coin"; Expression={$_.Pools.PSObject.Properties.Value | ForEach {"$($_.Coin)"}}}, + @{Name = "Coin"; Expression={$_.Pools.PSObject.Properties.Value | ForEach { + If($Variables.CoinsIconCacheLoaded) { + Try { + "  " + $_.Coin + } Catch {$_.Coin} + }else{$_.Coin} + }}}, + # @{Name = "Pool"; Expression={$_.Pools.PSObject.Properties.Value | ForEach {"$($_.Name)"}}}, + @{Name = "Pool"; Expression={$_.Pools.PSObject.Properties.Value | ForEach {"  " + $_.Name}}}, + @{Name = "Speed"; Expression={$_.HashRates.PSObject.Properties.Value | ForEach {if($_ -ne $null){"$($_ | ConvertTo-Hash)/s"}else{"Benchmarking"}}}}, + # @{Name = "mBTC/Day"; Expression={$_.Profits.PSObject.Properties.Value | ForEach {if($_ -ne $null){($_*1000).ToString("N3")}else{"Benchmarking"}}}}, + @{Name = "mBTC/Day"; Expression={(($_.Profits.PSObject.Properties.Value | Measure -Sum).Sum *1000).ToString("N3")}}, + # @{Name = "BTC/Day"; Expression={$_.Profits.PSObject.Properties.Value | ForEach {if($_ -ne $null){$_.ToString("N5")}else{"Benchmarking"}}}}, + # @{Name = "BTC/Day"; Expression={(($_.Profits.PSObject.Properties.Value | Measure -Sum).Sum).ToString("N3")}}, + # @{Name = "BTC/GH/Day"; Expression={$_.Pools.PSObject.Properties.Value.Price | ForEach {($_*1000000000).ToString("N15")}}} + @{Name = "BTC/GH/Day"; Expression={(($_.Pools.PSObject.Properties.Value.Price | Measure -Sum).Sum *1000000000).ToString("N5")}} + # ) | sort "mBTC/Day" -Descending) | ConvertTo-Html -CssUri "http://$($Config.Server_ClientIP):$($Config.Server_ClientPort)/Includes/Web.css" -Title $Title -PreContent $Header + ) | sort "mBTC/Day" -Descending) + + $DisplayEstimations = If ($Config.ShowOnlyTopCoins){ + [System.Collections.ArrayList]@($DisplayEstimations | sort "mBTC/Day" -Descending | Group "Type","Algorithm" | % { $_.Group | select -First 1}) + } else { + $DisplayEstimations + } + + $Content += $DisplayEstimations | ConvertTo-Html -CssUri "./Includes/Web.css" -Title $Title + + } + + ForEach ($Type in ($Miners.Type | Sort -Unique)) { + $Content = $Content -Replace "$($Type)", "  $($Type)" + } + $Content += $Footer + $Content = [System.Web.HttpUtility]::HtmlDecode($Content) + + $StatusCode = [System.Net.HttpStatusCode]::OK + + + Break + } + "/SwitchingLog" { + $Title = "SwitchingLog" + $ContentType = $MIMETypes[".html"] + + If (Test-Path ".\Logs\switching.log"){$SwitchingArray = [System.Collections.ArrayList]@(@((get-content ".\Logs\switching.log" -First 1) , (get-content ".\logs\switching.log" -last 15)) | ConvertFrom-Csv | Select date,type,algo,coin,host -Last 13)} + $Content = $SwitchingArray | ConvertTo-Html -CssUri "./Includes/Web.css" -Title $Title -PreContent $Header + + $Content += $Footer + + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/Cmd-CleanIconCache" { + $ContentType = "text/html" + $Variables.CoinIcons = @() + $Variables.CoinsIconCacheLoaded = $False + $Variables.CoinsIconCachePopulating = $False + + $Title = "CleanIconCache" + $Content = "OK" + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/Cmd-Pause" { + $ContentType = "text/html" + $Variables.StatusText = "Pause Mining requested via API." + $Variables.Paused = $True + $Variables.RestartCycle = $True + + $Title = "Pause Command" + $Content = "OK" + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/Cmd-Mine" { + $ContentType = "text/html" + $Variables.StatusText = "Start Mining requested via API." + $Variables.Paused = $False + $Variables.LastDonated = (Get-Date).AddDays(-1).AddHours(1) + $Variables.RestartCycle = $True + + $Title = "Mine Command" + $Content = "OK" + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + "/Cmd-ResetPeers" { + $ContentType = "text/html" + If (Test-Path ".\Config\Peers.json") {Remove-Item -Recurse -Force ".\Config\Peers.json"} + + $Title = "Peers Reset" + $Content = "OK" + $StatusCode = [System.Net.HttpStatusCode]::OK + Break + } + default { + # Set index page + if ($Path -eq "/") { + $Path = "/index.html" + } + + # Check if there is a file with the requested path + $Filename = $BasePath + $Path + if (Test-Path $Filename -PathType Leaf -ErrorAction SilentlyContinue) { + # If the file is a powershell script, execute it and return the output. A $Parameters parameter is sent built from the query string + # Otherwise, just return the contents of the file + $File = Get-ChildItem $Filename + + if ($File.Extension -eq ".ps1") { + $Content = & $File.FullName -Parameters $Parameters + } + else { + $Content = Get-Content $Filename -Raw + + # Process server side includes for html files + # Includes are in the traditional '' format used by many web servers + if ($File.Extension -eq ".html") { + $IncludeRegex = [regex]'' + $IncludeRegex.Matches($Content) | Foreach-Object { + $IncludeFile = $BasePath + '/' + $_.Groups[1].Value + if (Test-Path $IncludeFile -PathType Leaf) { + $IncludeData = Get-Content $IncludeFile -Raw + $Content = $Content -replace $_.Value, $IncludeData + } + } + } + } + + # Set content type based on file extension + if ($MIMETypes.ContainsKey($File.Extension)) { + $ContentType = $MIMETypes[$File.Extension] + Switch ($File.Extension) { + ".png" {$Content = [System.IO.File]::ReadAllBytes($File.FullName)} + } + } + else { + # If it's an unrecognized file type, prompt for download + $ContentType = "application/octet-stream" + } + } + else { + $StatusCode = 404 + $ContentType = "text/html" + $Content = "URI '$Path' is not a valid resource. ($Filename)" + } + } + } + $HasContent = $content -ne $null + + If ($Content.GetType() -ne [byte[]]) { + [byte[]] $Buf = [System.Text.Encoding]::UTF8.GetBytes($Content) + } Else { + [byte[]] $Buf = $Content + } + + # $Buf = [Text.Encoding]::UTF8.GetBytes($Content) + $Hres.Headers.Add("Content-Type", $ContentType) + $HRes.ContentLength64 = $Buf.Length + $HRes.OutputStream.Write($Buf,0,$Buf.Length) + $HRes.OutputStream.Flush() + $HRes.OutputStream.Dispose() + $HRes.Close() + $Content = $null + $Buf = $null + # $ProxyCache | convertto-json | Out-File ".\logs\ProxyCache.json" + } + if ($Config.Server_Log) { + $LogEntry = [PSCustomObject]@{ + CacheHitRatio = $CacheHitsRatio + StatusCode = $StatusCode.value__ + Date = Get-date + ClientAddress = $ClientAddress + ClientPort = $ClientPort + Path = $Path + URL = $ProxURL + Content = $HasContent + AuthSuccess = $AuthSuccess + pid = $pid + } + $LogEntry | Export-Csv ".\Logs\Server.log" -NoTypeInformation -Append + rv LogEntry + $ProxURL = "" + } + $HCTemp = $null + } + Write-Host "Server stopping" + $ServerListener.Stop() + $ServerListener.Close() + # $Variables.Server.Runspace.Close() + # $Variables.Server.Dispose() + + }) + $Server.Runspace = $ServerRunspace + # $Variables.Server | Add-Member -Force @{ServerListener = $ServerListener} + $Variables.ServerRunspaceHandle = $Server.BeginInvoke() +} + diff --git a/Includes/Web.css b/Includes/Web.css new file mode 100644 index 0000000..f3cc3ad --- /dev/null +++ b/Includes/Web.css @@ -0,0 +1,93 @@ +h1, h5, th { + text-align: center; +} + +body { + background-color: #F0F0F0; +} + +table { + margin: auto; + /* font-family: Segoe UI; */ + font-family: 'MS Sans Serif', Geneva, sans-serif; + box-shadow: -5px 5px 5px #888; + /* border: thin ridge grey; */ + border: none; + margin-left: auto; + margin-right: auto; +} + +th { + background: #000000; + color: #fff; + max-width: 400px; + padding: 5px 10px; + font-weight: normal; +} + +td { + font-size: 11px; + padding: 5px 20px; + color: #000; +} + +tr { + background: #b8d1f3; +} + +tr:nth-child(even) { + background: #F0F0F0; +} + +tr:nth-child(odd) { + /* background: #f2a900; */ + /* background: #f8d175; */ + background: #F9F9F9; +} + +header { + overflow: auto; + background-color: #F9F9F9; + /* background: left / no-repeat url(https://raw.githubusercontent.com/MrPlusGH/NPlusMiner/master/Includes/NPM.png); */ + padding: 5px 20px; + color: #000; + text-align: center; + font-weight: bold; +} + +header img { + float: left; + padding: 8px; + height: 60px; + width: 60px; +} + +SectionTitle { + overflow: auto; + padding: 5px 20px; + color: #000; + text-align: center; + font-weight: bold; +} + +img { + vertical-align:middle; +} + +.right{ + float:right; +} + +.left{ + float:left; +} + +Footer{ + overflow: auto; + position:fixed; + bottom:0; + left:0; + width: 100%; + background-color: #F9F9F9; + font-size: x-small; +} \ No newline at end of file diff --git a/Logs/CoinIcons.json b/Logs/CoinIcons.json new file mode 100644 index 0000000000000000000000000000000000000000..b4078039fd031e958e9d616f336df60e57449e7c GIT binary patch literal 42818 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 Ox%_wbbM{kaKmQL_*gtLn literal 0 HcmV?d00001 diff --git a/NPlusMiner.ps1 b/NPlusMiner.ps1 index f847d80..9bf43e6 100644 --- a/NPlusMiner.ps1 +++ b/NPlusMiner.ps1 @@ -1,6 +1,6 @@ <# This file is part of NPlusMiner -Copyright (c) 2018-2019 MrPlus +Copyright (c) 2018-2020 MrPlus NPlusMiner is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,8 +19,8 @@ along with this program. If not, see . <# Product: NPlusMiner File: NPlusMiner.ps1 -version: 5.4.1 -version date: 20190809 +version: 7.3.2 +version date: 20200509 #> param( @@ -88,7 +88,7 @@ param( @" NPlusMiner -Copyright (c) 2018-2019 MrPlus +Copyright (c) 2018-2020 MrPlus This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it @@ -110,7 +110,22 @@ Write-Host -F Yellow " Copyright and license notices must be preserved." $Global:Config = [hashtable]::Synchronized(@{}) $Global:Config | Add-Member -Force @{ConfigFile = $ConfigFile} $Global:Variables = [hashtable]::Synchronized(@{}) - $Global:Variables | Add-Member -Force -MemberType ScriptProperty -Name 'StatusText' -Value{ $this._StatusText;$This._StatusText = @() } -SecondValue { If (!$this._StatusText){$this._StatusText=@()};$this._StatusText+=$args[0];$Variables | Add-Member -Force @{RefreshNeeded = $True} } + $Global:Variables | Add-Member -Force -MemberType ScriptProperty -Name 'StatusText' -Value{ $this._StatusText;$This._StatusText = [System.Collections.ArrayList]::Synchronized(@()) } -SecondValue { If (!$this._StatusText){$this._StatusText=[System.Collections.ArrayList]::Synchronized(@())};$this._StatusText+=$args[0];$Variables.RefreshNeeded = $True } + + # Set Console size + $ConsoleHeight = 50 + $ConsoleWidth = 160 + $pswindow = (get-host).ui.rawui + $newsize = $pswindow.buffersize + # $newsize.height = 3000 + $newsize.width = $ConsoleWidth + $pswindow.buffersize = $newsize + $newsize = $pswindow.windowsize + # $newsize.height = $ConsoleHeight + $newsize.width = $ConsoleWidth + $pswindow.windowsize = $newsize + + rv ConsoleHeight, ConsoleWidth, pswindow, newsize # Load Branding If (Test-Path ".\Config\Branding.json") { @@ -155,6 +170,12 @@ Function Global:TimerUITick If ($true) { #$_.Pool -in ($config.PoolName -replace "24hr","" -replace "plus","")) { $Variables.EarningsPool = $_.Pool $Variables.Earnings.($_.Pool) = $_ + $Variables.Earnings.($_.Pool).PaymentThreshold = + If ($Config.PoolsConfig.($_.Pool).PayoutThreshold.($Config.Passwordcurrency)) { + $Config.PoolsConfig.($_.Pool).PayoutThreshold.($Config.Passwordcurrency) + } Else { + $_.PaymentThreshold + } } } rv EarnTrack @@ -165,7 +186,7 @@ Function Global:TimerUITick (compare -ReferenceObject $CheckedListBoxPools.Items -DifferenceObject ((Get-ChildItem ".\Pools").BaseName | sort -Unique) | ? {$_.SideIndicator -eq "=>"}).InputObject | % { if ($_ -ne $null){}$CheckedListBoxPools.Items.AddRange($_)} $Config.PoolName | foreach {$CheckedListBoxPools.SetItemChecked($CheckedListBoxPools.Items.IndexOf($_),$True)} } - $Variables | Add-Member -Force @{InCycle = $True} + $Variables.InCycle = $True # $MainForm.Number+=1 $MainForm.Text = $Branding.ProductLable + " " + $Variables.CurrentVersion + " Runtime " + ("{0:dd\ \d\a\y\s\ hh\:mm}" -f ((get-date)-$Variables.ScriptStartDate)) + " Path: " + (Split-Path $script:MyInvocation.MyCommand.Path) $host.UI.RawUI.WindowTitle = $Branding.ProductLable + " " + $Variables.CurrentVersion + " Runtime " + ("{0:dd\ \d\a\y\s\ hh\:mm}" -f ((get-date)-$Variables.ScriptStartDate)) + " Path: " + (Split-Path $script:MyInvocation.MyCommand.Path) @@ -184,13 +205,13 @@ Function Global:TimerUITick # https://stackoverflow.com/questions/8466343/why-controls-do-not-want-to-get-removed If (Test-Path ".\logs\DailyEarnings.csv"){ - $Chart1 = Invoke-Expression -Command ".\Includes\Charting.ps1 -Chart 'Front7DaysEarnings' -Width 505 -Height 85" + $Chart1 = Invoke-Expression -Command ".\Includes\Charting.ps1 -Chart 'Front7DaysEarnings' -Width 505 -Height 85 -Currency $($Config.Passwordcurrency)" $Chart1.top = 74 $Chart1.left = 0 $RunPage.Controls.Add($Chart1) $Chart1.BringToFront() - $Chart2 = Invoke-Expression -Command ".\Includes\Charting.ps1 -Chart 'DayPoolSplit' -Width 200 -Height 85" + $Chart2 = Invoke-Expression -Command ".\Includes\Charting.ps1 -Chart 'DayPoolSplit' -Width 200 -Height 85 -Currency $($Config.Passwordcurrency)" $Chart2.top = 74 $Chart2.left = 500 $RunPage.Controls.Add($Chart2) @@ -206,15 +227,15 @@ Function Global:TimerUITick @{Name="Balance";Expression={$_.Balance}}, # @{Name="Unpaid";Expression={$_.total_unpaid}}, # @{Name="BTC/D";Expression={"{0:N8}" -f ($_.BTCD)}}, - @{Name="1h m$([char]0x20BF)/D";Expression={"{0:N3}" -f ($_.Growth1*1000*24)}}, - @{Name="6h m$([char]0x20BF)/D";Expression={"{0:N3}" -f ($_.Growth6*1000*4)}}, - @{Name="24h m$([char]0x20BF)/D";Expression={"{0:N3}" -f ($_.Growth24*1000)}}, + @{Name="1h $((Get-DisplayCurrency $_.Growth1 24).UnitStringPerDay)";Expression={(Get-DisplayCurrency $_.Growth1 24).RoundedValue}}, + @{Name="6h $((Get-DisplayCurrency $_.Growth6 4).UnitStringPerDay)";Expression={(Get-DisplayCurrency $_.Growth6 4).RoundedValue}}, + @{Name="24h $((Get-DisplayCurrency $_.Growth24).UnitStringPerDay)";Expression={(Get-DisplayCurrency $_.Growth24).RoundedValue}}, @{Name = "Est. Pay Date"; Expression = {if ($_.EstimatedPayDate -is 'DateTime') {$_.EstimatedPayDate.ToShortDateString()} else {$_.EstimatedPayDate}}}, @{Name="PaymentThreshold";Expression={"$($_.PaymentThreshold) ($('{0:P0}' -f $($_.Balance / $_.PaymentThreshold)))"}}#, # @{Name="Wallet";Expression={$_.Wallet}} - ) | Sort "1h m$([char]0x20BF)/D","6h m$([char]0x20BF)/D","24h m$([char]0x20BF)/D" -Descending) + ) | Sort "1h $((Get-DisplayCurrency $_.Growth1 24).UnitStringPerDay)","6h $((Get-DisplayCurrency $_.Growth6 4).UnitStringPerDay)","24h $((Get-DisplayCurrency $_.Growth24).UnitStringPerDay)" -Descending) $EarningsDGV.DataSource = [System.Collections.ArrayList]@($DisplayEarnings) $EarningsDGV.ClearSelection() } @@ -227,12 +248,15 @@ Function Global:TimerUITick @{Name = "Coin"; Expression={$_.Pools.PSObject.Properties.Value | ForEach {"$($_.Info)"}}}, @{Name = "Pool"; Expression={$_.Pools.PSObject.Properties.Value | ForEach {"$($_.Name)"}}}, @{Name = "Speed"; Expression={$_.HashRates.PSObject.Properties.Value | ForEach {if($_ -ne $null){"$($_ | ConvertTo-Hash)/s"}else{"Benchmarking"}}}}, - @{Name = "mBTC/Day"; Expression={$_.Profits.PSObject.Properties.Value*1000 | ForEach {if($_ -ne $null){$_.ToString("N3")}else{"Benchmarking"}}}}, - @{Name = "BTC/Day"; Expression={$_.Profits.PSObject.Properties.Value | ForEach {if($_ -ne $null){$_.ToString("N5")}else{"Benchmarking"}}}}, - @{Name = "BTC/GH/Day"; Expression={$_.Pools.PSObject.Properties.Value.Price | ForEach {($_*1000000000).ToString("N5")}}} + # @{Name = "mBTC/Day"; Expression={$_.Profits.PSObject.Properties.Value | ForEach {if($_ -ne $null){($_*1000).ToString("N3")}else{"Benchmarking"}}}}, + @{Name = "mBTC/Day"; Expression={(($_.Profits.PSObject.Properties.Value | Measure -Sum).Sum *1000).ToString("N3")}}, + # @{Name = "BTC/Day"; Expression={$_.Profits.PSObject.Properties.Value | ForEach {if($_ -ne $null){$_.ToString("N5")}else{"Benchmarking"}}}}, + @{Name = "BTC/Day"; Expression={(($_.Profits.PSObject.Properties.Value | Measure -Sum).Sum).ToString("N3")}}, + # @{Name = "BTC/GH/Day"; Expression={$_.Pools.PSObject.Properties.Value.Price | ForEach {($_*1000000000).ToString("N15")}}} + @{Name = "BTC/GH/Day"; Expression={(($_.Pools.PSObject.Properties.Value.Price | Measure -Sum).Sum *1000000000).ToString("N5")}} ) | sort "mBTC/Day" -Descending) If ($Config.ShowOnlyTopCoins){ - $EstimationsDGV.DataSource = [System.Collections.ArrayList]@($DisplayEstimations | sort "mBTC/Day" -Descending | Group "Algorithm" | % { $_.Group | select -First 1} | sort "mBTC/Day" -Descending) + $EstimationsDGV.DataSource = [System.Collections.ArrayList]@($DisplayEstimations | sort "mBTC/Day" -Descending | Group "Type","Algorithm" | % { $_.Group | select -First 1} | sort "mBTC/Day" -Descending) } else { $EstimationsDGV.DataSource = [System.Collections.ArrayList]@($DisplayEstimations) } @@ -278,7 +302,17 @@ Function Global:TimerUITick } If ($Variables.ActiveMinerPrograms) { - $RunningMinersDGV.DataSource = [System.Collections.ArrayList]@($Variables.ActiveMinerPrograms | ? {$_.Status -eq "Running"} | select Type,Algorithms,Coin,Name,@{Name="HashRate";Expression={"$($_.HashRate | ConvertTo-Hash)/s"}},@{Name="Active";Expression={"{0:hh}:{0:mm}:{0:ss}" -f $_.Active}},@{Name="Total Active";Expression={"{0:hh}:{0:mm}:{0:ss}" -f $_.TotalActive}},Host | sort Type) + # $RunningMinersDGV.DataSource = [System.Collections.ArrayList]@($Variables.ActiveMinerPrograms | ? {$_.Status -eq "Running"} | select Type,Algorithms,Coin,Name,@{Name="HashRate";Expression={"$($_.HashRate | ConvertTo-Hash)/s"}},@{Name="Active";Expression={"{0:hh}:{0:mm}:{0:ss}" -f $_.Active}},@{Name="Total Active";Expression={"{0:hh}:{0:mm}:{0:ss}" -f $_.TotalActive}},Host | sort Type) + $RunningMinersDGV.DataSource = [System.Collections.ArrayList]@($Variables.ActiveMinerPrograms.Clone() | ? {$_.Status -eq "Running"} | select @( + @{Name = "Type";Expression={$_.Type}}, + @{Name = "Algorithm";Expression={$_.Algorithms}}, + @{Name = "Coin"; Expression={$_.Coin}}, + @{Name = "Miner";Expression={$_.Name}}, + @{Name="HashRate";Expression={"$($_.HashRate | ConvertTo-Hash)/s"}}, + @{Name ="Active";Expression={"{0:hh}:{0:mm}:{0:ss}" -f $_.Active}}, + @{Name ="Total Active";Expression={"{0:hh}:{0:mm}:{0:ss}" -f $_.TotalActive}}, + @{Name = "Pool"; Expression={$_.Pools.PSObject.Properties.Value | ForEach {"$($_.Name)"}}} ) | sort Type + ) $RunningMinersDGV.ClearSelection() [Array] $processRunning = $Variables.ActiveMinerPrograms | Where { $_.Status -eq "Running" } @@ -286,12 +320,13 @@ Function Global:TimerUITick # Update-Status("No miner running") } } - $LabelBTCPrice.text = If($Variables.Rates.$Currency -gt 0){"BTC/$($Config.Currency) $($Variables.Rates.($Config.Currency))"} - $Variables | Add-Member -Force @{InCycle = $False} + $LabelBTCPrice.text = If($Variables.Rates.$Currency -gt 0){"$($Config.Passwordcurrency)/$($Config.Currency) $($Variables.Rates.($Config.Currency))"} + $Variables.InCycle = $False If ($Variables.Earnings.Values -ne $Null){ - $LabelBTCD.Text = "Avg: " +("{0:N6}" -f ($Variables.Earnings.Values | measure -Property Growth24 -Sum).sum) + " $([char]0x20BF)/D | " + ("{0:N3}" -f (($Variables.Earnings.Values | measure -Property Growth24 -Sum).sum*1000)) + " m$([char]0x20BF)/D" + $LabelBTCD.Text = "Avg: " + ((Get-DisplayCurrency ($Variables.Earnings.Values | measure -Property Growth24 -Sum).sum)).DisplayStringPerDay + $LabelEarningsDetails.Lines = @() # If ((($Variables.Earnings.Values | measure -Property Growth1 -Sum).sum*1000*24) -lt ((($Variables.Earnings.Values | measure -Property BTCD -Sum).sum*1000)*0.999)) { @@ -304,7 +339,7 @@ Function Global:TimerUITick {$_ -lt 0} {"<"} } - $LabelEarningsDetails.Lines += "Last 1h: " + ("{0:N3}" -f (($Variables.Earnings.Values | measure -Property Growth1 -Sum).sum*1000*24)) + " m$([char]0x20BF)/D " + $TrendSign + $LabelEarningsDetails.Lines += "Last 1h: " + ((Get-DisplayCurrency ($Variables.Earnings.Values | measure -Property Growth1 -Sum).sum 24)).DisplayStringPerDay + " " + $TrendSign $TrendSign = switch ([Math]::Round((($Variables.Earnings.Values | measure -Property Growth6 -Sum).sum*1000*4),3) - [Math]::Round((($Variables.Earnings.Values | measure -Property Growth24 -Sum).sum*1000),3)) { {$_ -eq 0} {"="} @@ -313,7 +348,7 @@ Function Global:TimerUITick {$_ -lt 0} {"<"} } - $LabelEarningsDetails.Lines += "Last 6h: " + ("{0:N3}" -f (($Variables.Earnings.Values | measure -Property Growth6 -Sum).sum*1000*4)) + " m$([char]0x20BF)/D " + $TrendSign + $LabelEarningsDetails.Lines += "Last 6h: " + ((Get-DisplayCurrency ($Variables.Earnings.Values | measure -Property Growth6 -Sum).sum 4)).DisplayStringPerDay + " " + $TrendSign $TrendSign = switch ([Math]::Round((($Variables.Earnings.Values | measure -Property Growth24 -Sum).sum*1000),3) - [Math]::Round((($Variables.Earnings.Values | measure -Property BTCD -Sum).sum*1000*0.96),3)) { {$_ -eq 0} {"="} @@ -322,7 +357,7 @@ Function Global:TimerUITick {$_ -lt 0} {"<"} } - $LabelEarningsDetails.Lines += "Last 24h: " + ("{0:N3}" -f (($Variables.Earnings.Values | measure -Property Growth24 -Sum).sum*1000)) + " m$([char]0x20BF)/D " + $TrendSign + $LabelEarningsDetails.Lines += "Last 24h: " + ((Get-DisplayCurrency ($Variables.Earnings.Values | measure -Property Growth24 -Sum).sum)).DisplayStringPerDay + " " + $TrendSign rv TrendSign } else { $LabelBTCD.Text = "Waiting data from pools." @@ -332,9 +367,9 @@ Function Global:TimerUITick if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} if (!(IsLoaded(".\Includes\Core.ps1"))) {. .\Includes\Core.ps1;RegisterLoaded(".\Includes\Core.ps1")} - $Variables | Add-Member -Force @{CurrentProduct = (Get-Content .\Version.json | ConvertFrom-Json).Product} - $Variables | Add-Member -Force @{CurrentVersion = [Version](Get-Content .\Version.json | ConvertFrom-Json).Version} - $Variables | Add-Member -Force @{CurrentVersionAutoUpdated = (Get-Content .\Version.json | ConvertFrom-Json).AutoUpdated.Value} + $Variables.CurrentProduct = (Get-Content .\Version.json | ConvertFrom-Json).Product + $Variables.CurrentVersion = [Version](Get-Content .\Version.json | ConvertFrom-Json).Version + $Variables.CurrentVersionAutoUpdated = (Get-Content .\Version.json | ConvertFrom-Json).AutoUpdated.Value if ((Get-Content .\Version.json | ConvertFrom-Json).AutoUpdated -and $LabelNotifications.Lines[$LabelNotifications.Lines.Count-1] -ne "Auto Updated on $($Variables.CurrentVersionAutoUpdated)"){ $LabelNotifications.ForeColor = "Green" Update-Notifications("Running $($Variables.CurrentProduct) Version $([Version]$Variables.CurrentVersion)") @@ -391,7 +426,7 @@ Function Global:TimerUITick # ) | Out-Host If ($Config.ShowOnlyTopCoins){ - $DisplayEstimations | sort "mBTC/Day" -Descending | Group "Algorithm" | % { $_.Group | select -First 1} | sort Type,"mBTC/Day" -Descending | Format-Table -GroupBy Type | Out-Host + $DisplayEstimations | sort "mBTC/Day" -Descending | Group "Type","Algorithm" | % { $_.Group | select -First 1} | sort Type,"mBTC/Day" -Descending | Format-Table -GroupBy Type | Out-Host } else { $DisplayEstimations | sort Type,"mBTC/Day" -Descending | Format-Table -GroupBy Type | Out-Host } @@ -509,12 +544,23 @@ Function PrepareWriteConfig{ $Config | Add-Member -Force @{$_.Tag = [Int]$_.Text} } $Config | Add-Member -Force @{$CheckedListBoxPools.Tag = $CheckedListBoxPools.CheckedItems} + + $ServermodePageControls | ? {(($_.gettype()).Name -eq "CheckBox")} | foreach {$Config | Add-Member -Force @{$_.Tag = $_.Checked}} + $ServermodePageControls | ? {(($_.gettype()).Name -eq "TextBox")} | foreach {$Config | Add-Member -Force @{$_.Tag = $_.Text}} $MonitoringSettingsControls | ? {(($_.gettype()).Name -eq "CheckBox")} | foreach {$Config | Add-Member -Force @{$_.Tag = $_.Checked}} $MonitoringSettingsControls | ? {(($_.gettype()).Name -eq "TextBox")} | foreach {$Config | Add-Member -Force @{$_.Tag = $_.Text}} Write-Config -ConfigFile $ConfigFile -Config $Config $Config = Load-Config -ConfigFile $ConfigFile + + $ServerPasswd = ConvertTo-SecureString $Config.Server_Password -AsPlainText -Force + $ServerCreds = New-Object System.Management.Automation.PSCredential ($Config.Server_User, $ServerPasswd) + $Variables.ServerCreds = $ServerCreds + $ServerClientPasswd = ConvertTo-SecureString $Config.Server_ClientPassword -AsPlainText -Force + $ServerClientCreds = New-Object System.Management.Automation.PSCredential ($Config.Server_ClientUser, $ServerClientPasswd) + $Variables.ServerClientCreds = $ServerClientCreds + $MainForm.Refresh # [windows.forms.messagebox]::show("Please restart NPlusMiner",'Config saved','ok','Information') | out-null } @@ -653,9 +699,9 @@ $SelGPUDSTM = $Config.SelGPUDSTM $SelGPUCC = $Config.SelGPUCC $MainForm | Add-Member -Name "Variables" -Value $Variables -MemberType NoteProperty -Force -$Variables | Add-Member -Force @{CurrentProduct = (Get-Content .\Version.json | ConvertFrom-Json).Product} -$Variables | Add-Member -Force @{CurrentVersion = [Version](Get-Content .\Version.json | ConvertFrom-Json).Version} -$Variables | Add-Member -Force @{CurrentVersionAutoUpdated = (Get-Content .\Version.json | ConvertFrom-Json).AutoUpdated.Value} +$Variables.CurrentProduct = (Get-Content .\Version.json | ConvertFrom-Json).Product +$Variables.CurrentVersion = [Version](Get-Content .\Version.json | ConvertFrom-Json).Version +$Variables.CurrentVersionAutoUpdated = (Get-Content .\Version.json | ConvertFrom-Json).AutoUpdated.Value $Variables.StatusText = "Idle" $TabControl = New-object System.Windows.Forms.TabControl @@ -674,6 +720,8 @@ $MonitoringPage = New-Object System.Windows.Forms.TabPage $MonitoringPage.Text = "Monitoring" $EstimationsPage = New-Object System.Windows.Forms.TabPage $EstimationsPage.Text = "Benchmarks" +$ServerModePage = New-Object System.Windows.Forms.TabPage +$ServerModePage.Text = "Server Mode" $tabControl.DataBindings.DefaultDataSourceUpdateMode = 0 $tabControl.Location = New-Object System.Drawing.Point(10,91) @@ -681,7 +729,7 @@ $tabControl.Name = "tabControl" $tabControl.width = 720 $tabControl.height = 359 $MainForm.Controls.Add($tabControl) -$TabControl.Controls.AddRange(@($RunPage, $SwitchingPage, $ConfigPage, $MonitoringPage, $EstimationsPage)) +$TabControl.Controls.AddRange(@($RunPage, $SwitchingPage, $ConfigPage, $MonitoringPage, $EstimationsPage, $ServerModePage)) # Form Controls $MainFormControls = @() @@ -700,7 +748,7 @@ $TabControl.Controls.AddRange(@($RunPage, $SwitchingPage, $ConfigPage, $Monitori $LabelEarningsDetails.MultiLine = $true $LabelEarningsDetails.text = "" $LabelEarningsDetails.AutoSize = $false - $LabelEarningsDetails.width = 200 #382 + $LabelEarningsDetails.width = 382 #200 $LabelEarningsDetails.height = 47 #62 $LabelEarningsDetails.location = New-Object System.Drawing.Point(57,2) $LabelEarningsDetails.Font = 'lucida console,10' @@ -813,13 +861,13 @@ $TabControl.Controls.AddRange(@($RunPage, $SwitchingPage, $ConfigPage, $Monitori $RunPageControls += $LabelEarnings If (Test-Path ".\logs\DailyEarnings.csv"){ - $Chart1 = Invoke-Expression -Command ".\Includes\Charting.ps1 -Chart 'Front7DaysEarnings' -Width 505 -Height 85" + $Chart1 = Invoke-Expression -Command ".\Includes\Charting.ps1 -Chart 'Front7DaysEarnings' -Width 505 -Height 85 -Currency $($Config.Passwordcurrency)" $Chart1.top = 74 $Chart1.left = 2 $RunPageControls += $Chart1 } If (Test-Path ".\logs\DailyEarnings.csv"){ - $Chart2 = Invoke-Expression -Command ".\Includes\Charting.ps1 -Chart 'DayPoolSplit' -Width 200 -Height 85" + $Chart2 = Invoke-Expression -Command ".\Includes\Charting.ps1 -Chart 'DayPoolSplit' -Width 200 -Height 85 -Currency $($Config.Passwordcurrency)" $Chart2.top = 74 $Chart2.left = 500 $RunPageControls += $Chart2 @@ -854,10 +902,21 @@ $TabControl.Controls.AddRange(@($RunPage, $SwitchingPage, $ConfigPage, $Monitori $LabelCopyright.Size = New-Object System.Drawing.Size(200,20) $LabelCopyright.LinkColor = "BLUE" $LabelCopyright.ActiveLinkColor = "BLUE" - $LabelCopyright.Text = "Copyright (c) 2018-2019 MrPlus" + $LabelCopyright.Text = "Copyright (c) 2018-2020 MrPlus" $LabelCopyright.add_Click({[system.Diagnostics.Process]::start("https://github.com/MrPlusGH/NPlusMiner/blob/master/LICENSE")}) $RunPageControls += $LabelCopyright + $LabelWebUI = New-Object System.Windows.Forms.LinkLabel + # $LabelWebUI.Location = New-Object System.Drawing.Size(415,61) + # $LabelWebUI.Size = New-Object System.Drawing.Size(200,20) + $LabelWebUI.Location = New-Object System.Drawing.Size(250,246) + $LabelWebUI.Size = New-Object System.Drawing.Size(200,20) + $LabelWebUI.LinkColor = "BLUE" + $LabelWebUI.ActiveLinkColor = "BLUE" + $LabelWebUI.Text = "Web interface" + $LabelWebUI.add_Click({[system.Diagnostics.Process]::start("http://$($Config.Server_ClientIP):$($Config.Server_ClientPort)/Status")}) + $RunPageControls += $LabelWebUI + $LabelRunningMiners = New-Object system.Windows.Forms.Label $LabelRunningMiners.text = "Running Miners" $LabelRunningMiners.AutoSize = $false @@ -968,6 +1027,202 @@ $TabControl.Controls.AddRange(@($RunPage, $SwitchingPage, $ConfigPage, $Monitori # $DblBuff = ($EstimationsDGV.GetType()).GetProperty("DoubleBuffered", ('Instance','NonPublic')) # $DblBuff.SetValue($MainForm, $Truen, $null) +# Server mode Page Controls + $ServermodePageControls = @() + + $CheckBoxStandalone = New-Object system.Windows.Forms.CheckBox + $CheckBoxStandalone.Tag = "Server_Standalone" + $CheckBoxStandalone.text = "Standalone" + $CheckBoxStandalone.AutoSize = $false + $CheckBoxStandalone.width = 200 + $CheckBoxStandalone.height = 20 + $CheckBoxStandalone.location = New-Object System.Drawing.Point(2,2) + $CheckBoxStandalone.Font = 'Microsoft Sans Serif,10' + $CheckBoxStandalone.Checked = $Config.Server_Standalone + $ServermodePageControls += $CheckBoxStandalone + + $CheckBoxBeAServer = New-Object system.Windows.Forms.CheckBox + $CheckBoxBeAServer.Tag = "Server_On" + $CheckBoxBeAServer.text = "Be a server" + $CheckBoxBeAServer.AutoSize = $false + $CheckBoxBeAServer.width = 140 + $CheckBoxBeAServer.height = 20 + $CheckBoxBeAServer.location = New-Object System.Drawing.Point(2,24) + $CheckBoxBeAServer.Font = 'Microsoft Sans Serif,10' + $CheckBoxBeAServer.Checked = $Config.Server_On + $ServermodePageControls += $CheckBoxBeAServer + + $LabelServerPort = New-Object system.Windows.Forms.Label + $LabelServerPort.text = "Server Port" + $LabelServerPort.AutoSize = $false + $LabelServerPort.width = 120 + $LabelServerPort.height = 20 + $LabelServerPort.location = New-Object System.Drawing.Point(2,48) + $LabelServerPort.Font = 'Microsoft Sans Serif,10' + # $LabelServerPort.Enabled = $CheckBoxBeAServer.Checked + $ServermodePageControls += $LabelServerPort + + $TBServerPort = New-Object system.Windows.Forms.TextBox + $TBServerPort.Tag = "Server_Port" + $TBServerPort.MultiLine = $False + # $TBServerPort.Scrollbars = "Vertical" + $TBServerPort.text = $Config.Server_Port + $TBServerPort.AutoSize = $false + $TBServerPort.width = 50 + $TBServerPort.height = 20 + $TBServerPort.location = New-Object System.Drawing.Point(122,48) + $TBServerPort.Font = 'Microsoft Sans Serif,10' + # $TBServerPort.Enabled = $CheckBoxBeAServer.Checked + $ServermodePageControls += $TBServerPort + + $LabelServerUser = New-Object system.Windows.Forms.Label + $LabelServerUser.text = "Server User" + $LabelServerUser.AutoSize = $false + $LabelServerUser.width = 120 + $LabelServerUser.height = 20 + $LabelServerUser.location = New-Object System.Drawing.Point(2,72) + $LabelServerUser.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $LabelServerUser + + $TBServerUser = New-Object system.Windows.Forms.TextBox + $TBServerUser.Tag = "Server_User" + $TBServerUser.MultiLine = $False + # $TBServerIP.Scrollbars = "Vertical" + $TBServerUser.text = $Config.Server_User + $TBServerUser.AutoSize = $false + $TBServerUser.width = 150 + $TBServerUser.height = 20 + $TBServerUser.location = New-Object System.Drawing.Point(122,72) + $TBServerUser.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $TBServerUser + + $LabelServerPassword = New-Object system.Windows.Forms.Label + $LabelServerPassword.text = "Server Password" + $LabelServerPassword.AutoSize = $false + $LabelServerPassword.width = 120 + $LabelServerPassword.height = 20 + $LabelServerPassword.location = New-Object System.Drawing.Point(274,72) + $LabelServerPassword.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $LabelServerPassword + + $TBServerPassword = New-Object system.Windows.Forms.TextBox + $TBServerPassword.Tag = "Server_Password" + $TBServerPassword.MultiLine = $False + # $TBServerPassword.Scrollbars = "Vertical" + $TBServerPassword.text = if ($Config.Server_Password) {$Config.Server_Password} else {(New-Guid).guid} + $TBServerPassword.AutoSize = $false + $TBServerPassword.width = 150 + $TBServerPassword.height = 20 + $TBServerPassword.location = New-Object System.Drawing.Point(396,72) + $TBServerPassword.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $TBServerPassword + + $CheckBoxServerClient = New-Object system.Windows.Forms.CheckBox + $CheckBoxServerClient.Tag = "Server_Client" + $CheckBoxServerClient.text = "Use a server" + $CheckBoxServerClient.AutoSize = $false + $CheckBoxServerClient.width = 140 + $CheckBoxServerClient.height = 20 + $CheckBoxServerClient.location = New-Object System.Drawing.Point(2,94) + $CheckBoxServerClient.Font = 'Microsoft Sans Serif,10' + $CheckBoxServerClient.Checked = $Config.Server_Client + $ServermodePageControls += $CheckBoxServerClient + + $LabelServerClientIP = New-Object system.Windows.Forms.Label + $LabelServerClientIP.text = "Server IP" + $LabelServerClientIP.AutoSize = $false + $LabelServerClientIP.width = 120 + $LabelServerClientIP.height = 20 + $LabelServerClientIP.location = New-Object System.Drawing.Point(2,116) + $LabelServerClientIP.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $LabelServerClientIP + + $TBServerClientIP = New-Object system.Windows.Forms.TextBox + $TBServerClientIP.Tag = "Server_ClientIP" + $TBServerClientIP.MultiLine = $False + # $TBServerClientIP.Scrollbars = "Vertical" + $TBServerClientIP.text = $Config.Server_ClientIP + $TBServerClientIP.AutoSize = $false + $TBServerClientIP.width = 150 + $TBServerClientIP.height = 20 + $TBServerClientIP.location = New-Object System.Drawing.Point(122,116) + $TBServerClientIP.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $TBServerClientIP + + $LabelServerClientPort = New-Object system.Windows.Forms.Label + $LabelServerClientPort.text = "Port" + $LabelServerClientPort.AutoSize = $false + $LabelServerClientPort.width = 120 + $LabelServerClientPort.height = 20 + $LabelServerClientPort.location = New-Object System.Drawing.Point(274,116) + $LabelServerClientPort.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $LabelServerClientPort + + $TBServerClientPort = New-Object system.Windows.Forms.TextBox + $TBServerClientPort.Tag = "Server_ClientPort" + $TBServerClientPort.MultiLine = $False + # $TBServerClientPort.Scrollbars = "Vertical" + $TBServerClientPort.text = $Config.Server_ClientPort + $TBServerClientPort.AutoSize = $false + $TBServerClientPort.width = 50 + $TBServerClientPort.height = 20 + $TBServerClientPort.location = New-Object System.Drawing.Point(396,116) + $TBServerClientPort.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $TBServerClientPort + + $LabelServerClientUser = New-Object system.Windows.Forms.Label + $LabelServerClientUser.text = "Server User" + $LabelServerClientUser.AutoSize = $false + $LabelServerClientUser.width = 120 + $LabelServerClientUser.height = 20 + $LabelServerClientUser.location = New-Object System.Drawing.Point(2,138) + $LabelServerClientUser.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $LabelServerClientUser + + $TBServerClientUser = New-Object system.Windows.Forms.TextBox + $TBServerClientUser.Tag = "Server_ClientUser" + $TBServerClientUser.MultiLine = $False + # $TBServerClientUser.Scrollbars = "Vertical" + $TBServerClientUser.text = $Config.Server_ClientUser + $TBServerClientUser.AutoSize = $false + $TBServerClientUser.width = 150 + $TBServerClientUser.height = 20 + $TBServerClientUser.location = New-Object System.Drawing.Point(122,138) + $TBServerClientUser.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $TBServerClientUser + + $LabelServerClientPassword = New-Object system.Windows.Forms.Label + $LabelServerClientPassword.text = "Server Password" + $LabelServerClientPassword.AutoSize = $false + $LabelServerClientPassword.width = 120 + $LabelServerClientPassword.height = 20 + $LabelServerClientPassword.location = New-Object System.Drawing.Point(274,138) + $LabelServerClientPassword.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $LabelServerClientPassword + + $TBServerClientPassword = New-Object system.Windows.Forms.TextBox + $TBServerClientPassword.Tag = "Server_ClientPassword" + $TBServerClientPassword.MultiLine = $False + # $TBServerClientPassword.Scrollbars = "Vertical" + $TBServerClientPassword.text = $Config.Server_ClientPassword + $TBServerClientPassword.AutoSize = $false + $TBServerClientPassword.width = 150 + $TBServerClientPassword.height = 20 + $TBServerClientPassword.location = New-Object System.Drawing.Point(396,138) + $TBServerClientPassword.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $TBServerClientPassword + + $ButtonWriteServerConfig = New-Object system.Windows.Forms.Button + $ButtonWriteServerConfig.text = "Save Config" + $ButtonWriteServerConfig.width = 100 + $ButtonWriteServerConfig.height = 30 + $ButtonWriteServerConfig.location = New-Object System.Drawing.Point(610,300) + $ButtonWriteServerConfig.Font = 'Microsoft Sans Serif,10' + $ServermodePageControls += $ButtonWriteServerConfig + + $ButtonWriteServerConfig.Add_Click({PrepareWriteConfig}) + + # Config Page Controls $ConfigPageControls = @() @@ -1141,7 +1396,7 @@ $TabControl.Controls.AddRange(@($RunPage, $SwitchingPage, $ConfigPage, $Monitori $ConfigPageControls += $TBCurrency $LabelPwdCurrency = New-Object system.Windows.Forms.Label - $LabelPwdCurrency.text = "Pwd Currency" + $LabelPwdCurrency.text = "Payout Currency" $LabelPwdCurrency.AutoSize = $false $LabelPwdCurrency.width = 120 $LabelPwdCurrency.height = 20 @@ -1275,7 +1530,7 @@ $TabControl.Controls.AddRange(@($RunPage, $SwitchingPage, $ConfigPage, $Monitori $StartPort = 4068 Update-Status("Finding available TCP Port for $($This.Text)") $Port = Get-FreeTcpPort($StartPort) - $Variables | Add-Member -Force @{"$($This.Text)MinerAPITCPPort" = $Port} + $Variables."$($This.Text)MinerAPITCPPort" = $Port Update-Status("Miners API Port: $($Port)") $StartPort = $Port+1 } @@ -1301,7 +1556,7 @@ $TabControl.Controls.AddRange(@($RunPage, $SwitchingPage, $ConfigPage, $Monitori $StartPort = 4068 Update-Status("Finding available TCP Port for $($This.Text)") $Port = Get-FreeTcpPort($StartPort) - $Variables | Add-Member -Force @{"$($This.Text)MinerAPITCPPort" = $Port} + $Variables."$($This.Text)MinerAPITCPPort" = $Port Update-Status("Miners API Port: $($Port)") $StartPort = $Port+1 } @@ -1327,7 +1582,7 @@ $TabControl.Controls.AddRange(@($RunPage, $SwitchingPage, $ConfigPage, $Monitori $StartPort = 4068 Update-Status("Finding available TCP Port for $($This.Text)") $Port = Get-FreeTcpPort($StartPort) - $Variables | Add-Member -Force @{"$($This.Text)MinerAPITCPPort" = $Port} + $Variables."$($This.Text)MinerAPITCPPort" = $Port Update-Status("Miners API Port: $($Port)") $StartPort = $Port + 1 } @@ -1723,7 +1978,7 @@ $ButtonPause.Add_Click( { $LabelBTCD.Text = "Mining Paused | $($Branding.ProductLable) $($Variables.CurrentVersion)" If ($Variables.DonationRunning) { - $Variables | Add-Member -Force @{ DonationRunning = $False } + $Variables.DonationRunning = $False $ConfigLoad = Get-Content $Config.ConfigFile | ConvertFrom-json $ConfigLoad | % {$_.psobject.properties | sort Name | % {$Config | Add-Member -Force @{$_.Name = $_.Value}}} $Config | Add-Member -Force -MemberType ScriptProperty -Name "PoolsConfig" -Value { @@ -1746,7 +2001,7 @@ $ButtonPause.Add_Click( { else { $Variables.Paused = $False $ButtonPause.Text = "Pause" - $Variables | Add-Member -Force @{LastDonated = (Get-Date).AddDays(-1).AddHours(1)} + $Variables.LastDonated = (Get-Date).AddDays(-1).AddHours(1) $TimerUI.Start() # Stop and start mining to immediately switch to unpaused state without waiting for current sleep to finish @@ -1779,7 +2034,7 @@ $ButtonStart.Add_Click( { # $TimerUI.Interval = 1000 If ($Variables.DonationRunning) { - $Variables | Add-Member -Force @{ DonationRunning = $False } + $Variables.DonationRunning = $False $ConfigLoad = Get-Content $Config.ConfigFile | ConvertFrom-json $ConfigLoad | % {$_.psobject.properties | sort Name | % {$Config | Add-Member -Force @{$_.Name = $_.Value}}} $Config | Add-Member -Force -MemberType ScriptProperty -Name "PoolsConfig" -Value { @@ -1805,8 +2060,8 @@ $ButtonStart.Add_Click( { PrepareWriteConfig $ButtonStart.Text = "Stop" InitApplication - $Variables | add-Member -Force @{MainPath = (Split-Path $script:MyInvocation.MyCommand.Path)} - $Variables | Add-Member -Force @{LastDonated = (Get-Date).AddDays(-1).AddHours(1)} + $Variables.MainPath = (Split-Path $script:MyInvocation.MyCommand.Path) + $Variables.LastDonated = (Get-Date).AddDays(-1).AddHours(1) Start-IdleTracking @@ -1836,6 +2091,7 @@ $MainForm.controls.AddRange($MainFormControls) $RunPage.controls.AddRange(@($RunPageControls)) $SwitchingPage.controls.AddRange(@($SwitchingPageControls)) $EstimationsPage.Controls.AddRange(@($EstimationsDGV)) +$ServerModePage.Controls.AddRange($ServermodePageControls) $ConfigPage.controls.AddRange($ConfigPageControls) $GroupMonitoringSettings.Controls.AddRange($MonitoringSettingsControls) $MonitoringPage.controls.AddRange($MonitoringPageControls) diff --git a/Pools/ahashpool.ps1 b/Pools/ahashpool.ps1 new file mode 100644 index 0000000..3d15c76 --- /dev/null +++ b/Pools/ahashpool.ps1 @@ -0,0 +1,51 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { + $Request = Invoke-ProxiedWebRequest "https://www.ahashpool.com/api/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json +} +catch { return } + +if (-not $Request) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = ".mine.ahashpool.com" +# $PriceField = "actual_last24h" +$PriceField = "estimate_current" +$DivisorMultiplier = 1000000 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { + $PoolHost = "$($_)$($HostSuffix)" + $PoolPort = $Request.$_.port + $PoolAlgorithm = Get-Algorithm $Request.$_.name + + $Divisor = $DivisorMultiplier * [Double]$Request.$_.mbtc_mh_factor + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "$ahashpool_Coin $ahashpool_Coinname" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + } + } +} diff --git a/Pools/ahashpool24hr.ps1 b/Pools/ahashpool24hr.ps1 new file mode 100644 index 0000000..515414b --- /dev/null +++ b/Pools/ahashpool24hr.ps1 @@ -0,0 +1,51 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { + $Request = Invoke-ProxiedWebRequest "https://www.ahashpool.com/api/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json +} +catch { return } + +if (-not $Request) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = ".mine.ahashpool.com" +$PriceField = "actual_last24h" +# $PriceField = "estimate_current" +$DivisorMultiplier = 1000000000 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { + $PoolHost = "$($_)$($HostSuffix)" + $PoolPort = $Request.$_.port + $PoolAlgorithm = Get-Algorithm $Request.$_.name + + $Divisor = $DivisorMultiplier * [Double]$Request.$_.mbtc_mh_factor + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "$ahashpool_Coin $ahashpool_Coinname" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + } + } +} diff --git a/Pools/ahashpoolplus.ps1 b/Pools/ahashpoolplus.ps1 new file mode 100644 index 0000000..062ca66 --- /dev/null +++ b/Pools/ahashpoolplus.ps1 @@ -0,0 +1,57 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +Try { + $dtAlgos = New-Object System.Data.DataTable + if (Test-Path ((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\ahashpoolplus\ahashpoolplus.xml")) { + $dtAlgos.ReadXml((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\ahashpoolplus\ahashpoolplus.xml") | out-null + } +} +catch { return } + +if (-not $dtAlgos) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = ".mine.ahashpool.com" +$PriceField = "Plus_Price" +# $PriceField = "actual_last24h" +# $PriceField = "estimate_current" +$DivisorMultiplier = 1000000 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$dtAlgos | foreach { + $Pool = $_ + $PoolHost = "$($Pool.algo)$($HostSuffix)" + $PoolPort = $Pool.port + $PoolAlgorithm = Get-Algorithm $Pool.algo + + $Divisor = $DivisorMultiplier * [Double]$Pool.mbtc_mh_factor + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Pool.$PriceField / $Divisor * (1 - ($Pool.fees / 100)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Pool.$PriceField / $Divisor * (1 - ($Pool.fees / 100)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "Auto-($($Pool.symbol))" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + Coin = "Auto-($($Pool.symbol))" + } + } +} diff --git a/Pools/blazepool.ps1 b/Pools/blazepool.ps1 new file mode 100644 index 0000000..0d16dd7 --- /dev/null +++ b/Pools/blazepool.ps1 @@ -0,0 +1,51 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { + $Request = Invoke-ProxiedWebRequest "http://api.blazepool.com/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json +} +catch { return } + +if (-not $Request) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = ".mine.blazepool.com" +# $PriceField = "actual_last24h" +$PriceField = "estimate_current" +$DivisorMultiplier = 1000000 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { + $PoolHost = "$($_)$($HostSuffix)" + $PoolPort = $Request.$_.port + $PoolAlgorithm = Get-Algorithm $Request.$_.name + + $Divisor = $DivisorMultiplier * [Double]$Request.$_.mbtc_mh_factor + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + } + } +} diff --git a/Pools/blazepool24hr.ps1 b/Pools/blazepool24hr.ps1 new file mode 100644 index 0000000..9e70a60 --- /dev/null +++ b/Pools/blazepool24hr.ps1 @@ -0,0 +1,51 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { + $Request = Invoke-ProxiedWebRequest "http://api.blazepool.com/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json +} +catch { return } + +if (-not $Request) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = ".mine.blazepool.com" +$PriceField = "actual_last24h" +# $PriceField = "estimate_current" +$DivisorMultiplier = 1000000000 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { + $PoolHost = "$($_)$($HostSuffix)" + $PoolPort = $Request.$_.port + $PoolAlgorithm = Get-Algorithm $Request.$_.name + + $Divisor = $DivisorMultiplier * [Double]$Request.$_.mbtc_mh_factor + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + } + } +} diff --git a/Pools/blazepoolplus.ps1 b/Pools/blazepoolplus.ps1 new file mode 100644 index 0000000..e029f65 --- /dev/null +++ b/Pools/blazepoolplus.ps1 @@ -0,0 +1,57 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +Try { + $dtAlgos = New-Object System.Data.DataTable + if (Test-Path ((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\blazepoolplus\blazepoolplus.xml")) { + $dtAlgos.ReadXml((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\blazepoolplus\blazepoolplus.xml") | out-null + } +} +catch { return } + +if (-not $dtAlgos) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = ".mine.blazepool.com" +$PriceField = "Plus_Price" +# $PriceField = "actual_last24h" +# $PriceField = "estimate_current" +$DivisorMultiplier = 1000000 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$dtAlgos | foreach { + $Pool = $_ + $PoolHost = "$($Pool.algo)$($HostSuffix)" + $PoolPort = $Pool.port + $PoolAlgorithm = Get-Algorithm $Pool.algo + + $Divisor = $DivisorMultiplier * [Double]$Pool.mbtc_mh_factor + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Pool.$PriceField / $Divisor * (1 - ($Pool.fees / 100)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Pool.$PriceField / $Divisor * (1 - ($Pool.fees / 100)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "Auto($($Pool.symbol))" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + Coin = "Auto-($($Pool.symbol))" + } + } +} diff --git a/Pools/blockmasters.ps1 b/Pools/blockmasters.ps1 new file mode 100644 index 0000000..e9ce1c1 --- /dev/null +++ b/Pools/blockmasters.ps1 @@ -0,0 +1,61 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { + $Request = Invoke-ProxiedWebRequest "http://blockmasters.co/api/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json +} +catch { return } + +if (-not $Request) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = "blockmasters.co" +# $PriceField = "actual_last24h" +$PriceField = "estimate_current" + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { + $PoolHost = "$($HostSuffix)" + $PoolPort = $Request.$_.port + $PoolAlgorithm = Get-Algorithm $Request.$_.name + + $Divisor = 1000000 * [Double]$Request.$_.mbtc_mh_factor + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + $Locations = "eu.", "" + $Locations | ForEach-Object { + $Pool_Location = $_ + + switch ($Pool_Location) { + "eu." {$Location = "EU"} + "" {$Location = "US"} + } + $PoolHost = "$($Pool_Location)$($HostSuffix)" + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "$ahashpool_Coin $ahashpool_Coinname" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + } + } + } +} diff --git a/Pools/blockmasters24hr.ps1 b/Pools/blockmasters24hr.ps1 new file mode 100644 index 0000000..1821bd9 --- /dev/null +++ b/Pools/blockmasters24hr.ps1 @@ -0,0 +1,62 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { + $Request = Invoke-ProxiedWebRequest "http://blockmasters.co/api/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json +} +catch { return } + +if (-not $Request) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = "blockmasters.co" +$PriceField = "actual_last24h" +# $PriceField = "estimate_current" +$DivisorMultiplier = 1000000000 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { + $PoolHost = "$($HostSuffix)" + $PoolPort = $Request.$_.port + $PoolAlgorithm = Get-Algorithm $Request.$_.name + + $Divisor = $DivisorMultiplier * [Double]$Request.$_.mbtc_mh_factor + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + $Locations = "eu.", "" + $Locations | ForEach-Object { + $Pool_Location = $_ + + switch ($Pool_Location) { + "eu." {$Location = "EU"} + "" {$Location = "US"} + } + $PoolHost = "$($Pool_Location)$($HostSuffix)" + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "$ahashpool_Coin $ahashpool_Coinname" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + } + } + } +} diff --git a/Pools/blockmastersplus.ps1 b/Pools/blockmastersplus.ps1 new file mode 100644 index 0000000..244a4d5 --- /dev/null +++ b/Pools/blockmastersplus.ps1 @@ -0,0 +1,70 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +Try { + $dtAlgos = New-Object System.Data.DataTable + if (Test-Path ((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\blockmastersplus\blockmastersplus.xml")) { + $dtAlgos.ReadXml((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\blockmastersplus\blockmastersplus.xml") | out-null + } +} +catch { return } + +if (-not $dtAlgos) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = "blockmasters.co" +$PriceField = "Plus_Price" +# $PriceField = "actual_last24h" +# $PriceField = "estimate_current" +$DivisorMultiplier = 1000000 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$dtAlgos | foreach { + $Pool = $_ + $PoolHost = "$($HostSuffix)" + $PoolPort = $Pool.port + $PoolAlgorithm = Get-Algorithm $Pool.algo + + $Divisor = $DivisorMultiplier * [Double]$Pool.mbtc_mh_factor + + $Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Pool.$PriceField / $Divisor * (1 - ($Pool.fees / 100))) + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + $PoolPassword = If ( ! $Config.PartyWhenAvailable ) {"$($WorkerName),c=$($PwdCurr)"} else { "$($WorkerName),c=$($PwdCurr),m=PARTY.NPlusMiner" } + $PoolPassword = If ( $Pool.symbol ) { "$($PoolPassword),mc=$($Pool.symbol)" } else { $PoolPassword } + + $Locations = "eu.", "" + $Locations | ForEach-Object { + $Pool_Location = $_ + + switch ($Pool_Location) { + "eu." {$Location = "EU"} + default {$Location = "US"} + } + $PoolHost = "$($Pool_Location)$($HostSuffix)" + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = $Pool.symbol + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = $PoolPassword + Location = $Location + SSL = $false + Coin = $Pool.symbol + } + } + } +} diff --git a/Pools/miningpoolhub.ps1 b/Pools/miningpoolhub.ps1 new file mode 100644 index 0000000..d13e464 --- /dev/null +++ b/Pools/miningpoolhub.ps1 @@ -0,0 +1,65 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { $Request = Invoke-ProxiedWebRequest "https://miningpoolhub.com/index.php?page=api&action=getautoswitchingandprofitsstatistics" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json +} +catch { return } + +if (-not $Request.success) { + return +} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName + +$Locations = 'EU', 'US', 'Asia' +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$Fee = 0.0090 + (0.0003 / .01) #Fee + Withdrawal fee based on 0.01 BTC threshold +$Divisor = 1000000000 + + + $Request.return | ForEach-Object { + $Current = $_ + $Algorithm = Get-Algorithm($_.algo -replace "-") + $Coin = $_.current_mining_coin_symbol + + $Stat = Set-Stat -Name "$($Name)_$($Algorithm)_Profit" -Value ([decimal]$_.profit / $Divisor * (1 - $Fee)) + $Price = (($Stat.Live * (1 - [Math]::Min($Stat.Day_Fluctuation, 1))) + ($Stat.Day * (0 + [Math]::Min($Stat.Day_Fluctuation, 1)))) + +$Locations | ForEach-Object { + $Location = $_ + + [PSCustomObject]@{ + Algorithm = $Algorithm + Info = $Coin + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + Protocol = 'stratum+tcp' + Host = $Current.all_host_list.split(";") | Sort-Object -Descending {$_ -ilike "$Location*"} | Select-Object -First 1 + Port = $Current.algo_switch_port + User = "$($PoolConf.UserName).$($PoolConf.WorkerName.replace('ID=',''))" + Pass = 'x' + Location = $Location + SSL = $false + Coin = $Coin + } + + [PSCustomObject]@{ + Algorithm = $Algorithm + Info = $Coin + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + Protocol = 'stratum+ssl' + Host = $Current.all_host_list.split(";") | Sort-Object -Descending {$_ -ilike "$Location*"} | Select-Object -First 1 + Port = $Current.algo_switch_port + User = "$($PoolConf.UserName).$($PoolConf.WorkerName.replace('ID=',''))" + Pass = 'x' + Location = $Location + SSL = $true + coin = $Coin + } + } +} + + diff --git a/Pools/nicehash.ps1 b/Pools/nicehash.ps1 new file mode 100644 index 0000000..7438311 --- /dev/null +++ b/Pools/nicehash.ps1 @@ -0,0 +1,79 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { + $Request = Invoke-ProxiedWebRequest "https://api2.nicehash.com/main/api/v2/public/simplemultialgo/info/" | ConvertFrom-Json + $RequestAlgodetails = Invoke-ProxiedWebRequest "https://api2.nicehash.com/main/api/v2/mining/algorithms/" | ConvertFrom-Json +} +catch { return } + +if (-not $Request -or -not $RequestAlgodetails) {return} + +$Request.miningAlgorithms | foreach {$Algo = $_.Algorithm ; $_ | Add-Member -force @{algodetails = $RequestAlgodetails.miningAlgorithms | ? {$_.Algorithm -eq $Algo}}} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$Fees = 5 + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + + + $Request.miningAlgorithms| ? { [Double]$_.paying -gt 0 } | ForEach-Object { + $Algo = $_.Algorithm + $NiceHash_Port = $_.algodetails.port + $NiceHash_Algorithm = Get-Algorithm $_.Algorithm + $NiceHash_Coin = "" + + $DivisorMultiplier = 100000 + $Divisor = $DivisorMultiplier * [Double]$_.Algodetails.marketFactor + $Divisor = 100000000 + + $Stat = Set-Stat -Name "$($Name)_$($NiceHash_Algorithm)_Profit" -Value ([Double]$_.paying / $Divisor * (1 - ($Fees / 100))) + +$Locations = "eu", "usa", "hk", "jp", "in", "br" +$Locations | ForEach-Object { + $NiceHash_Location = $_ + + switch ($NiceHash_Location) { + "eu" {$Location = "EU"} + "usa" {$Location = "US"} + "jp" {$Location = "JP"} + "hk" {$Location = "JP"} + "in" {$Location = "JP"} + # "br" {$Location = "US"} + } + $NiceHash_Host = "$($Algo).$($NiceHash_Location).nicehash.com" + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $NiceHash_Algorithm + Info = $NiceHash_Coin + Price = $Stat."Minute_5"*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $NiceHash_Host + Port = $NiceHash_Port + User = "$($PoolConf.Wallet).$($PoolConf.WorkerName.Replace('ID=',''))" + Pass = "x" + Location = $Location + SSL = $false + } + + [PSCustomObject]@{ + Algorithm = $NiceHash_Algorithm + Info = $NiceHash_Coin + Price = $Stat."Minute_5"*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+ssl" + Host = $NiceHash_Host + Port = $NiceHash_Port + User = "$($PoolConf.Wallet).$($PoolConf.WorkerName.Replace('ID=',''))" + Pass = "x" + Location = $Location + SSL = $true + } + } + } +} diff --git a/Pools/nlpool.ps1 b/Pools/nlpool.ps1 new file mode 100644 index 0000000..e9c0544 --- /dev/null +++ b/Pools/nlpool.ps1 @@ -0,0 +1,57 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1; RegisterLoaded(".\Includes\include.ps1")} + +try { + $Request = Invoke-ProxiedWebRequest "http://www.nlpool.nl/api/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json +} +catch { return } + +if (-not $Request) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = "mine.nlpool.nl" +# $PriceField = "actual_last24h" +$PriceField = "estimate_current" + +$Location = "US" + +# Placed here for Perf (Disk reads) +$ConfName = if ($Config.PoolsConfig.$Name -ne $Null) {$Name}else {"default"} +$PoolConf = $Config.PoolsConfig.$ConfName + +$Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { + $PoolHost = $HostSuffix + $PoolPort = $Request.$_.port + $PoolAlgorithm = Get-Algorithm $Request.$_.name + + $Divisor = 1000000 * [Double]$Request.$_.mbtc_mh_factor + + switch ($PoolAlgorithm) { + # "equihash125" { $Divisor *= 2 } #temp fix + # "equihash144" { $Divisor *= 2 } #temp fix + # "equihash192" { $Divisor *= 2 } #temp fix + "verushash" { $Divisor *= 2 } #temp fix + } + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "" + Price = $Stat.Live * $PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + } + } +} diff --git a/Pools/nlpool24hr.ps1 b/Pools/nlpool24hr.ps1 new file mode 100644 index 0000000..d6e8ec0 --- /dev/null +++ b/Pools/nlpool24hr.ps1 @@ -0,0 +1,57 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { + $Request = Invoke-ProxiedWebRequest "http://www.nlpool.nl/api/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json +} +catch { return } + +if (-not $Request) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = "mine.nlpool.nl" +$PriceField = "actual_last24h" +# $PriceField = "estimate_current" + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { + $PoolHost = $HostSuffix + $PoolPort = $Request.$_.port + $PoolAlgorithm = Get-Algorithm $Request.$_.name + + $Divisor = 1000000000 * [Double]$Request.$_.mbtc_mh_factor + + switch ($PoolAlgorithm) { + # "equihash125" { $Divisor *= 2 } #temp fix + # "equihash144" { $Divisor *= 2 } #temp fix + # "equihash192" { $Divisor *= 2 } #temp fix + "verushash" { $Divisor *= 2 } #temp fix + } + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + } + } +} diff --git a/Pools/nlpoolplus.ps1 b/Pools/nlpoolplus.ps1 new file mode 100644 index 0000000..7b32cb5 --- /dev/null +++ b/Pools/nlpoolplus.ps1 @@ -0,0 +1,62 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +Try { + $dtAlgos = New-Object System.Data.DataTable + if (Test-Path ((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\nlpoolplus\nlpoolplus.xml")) { + $dtAlgos.ReadXml((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\nlpoolplus\nlpoolplus.xml") | out-null + } +} +catch { return } + +if (-not $dtAlgos) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = "mine.nlpool.nl" +$PriceField = "Plus_Price" +# $PriceField = "actual_last24h" +# $PriceField = "estimate_current" + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$dtAlgos | foreach { + $Pool = $_ + $PoolHost = $HostSuffix + $PoolPort = $Pool.port + $PoolAlgorithm = Get-Algorithm $Pool.algo + + $Divisor = 1000000 * [Double]$Pool.mbtc_mh_factor + + switch ($PoolAlgorithm) { + # "equihash125" { $Divisor *= 2 } #temp fix + # "equihash144" { $Divisor *= 2 } #temp fix + # "equihash192" { $Divisor *= 2 } #temp fix + "verushash" { $Divisor *= 2 } #temp fix + } + + $Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Pool.$PriceField / $Divisor * (1 - ($Pool.fees / 100))) + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "Auto($($Pool.symbol))" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + Coin = "Auto-($($Pool.symbol))" + } + } +} diff --git a/Pools/prohashing.ps1 b/Pools/prohashing.ps1 new file mode 100644 index 0000000..a122ba2 --- /dev/null +++ b/Pools/prohashing.ps1 @@ -0,0 +1,59 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { + $Headers = @{"Accept"="text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"} + $Request = ((Invoke-ProxiedWebRequest "https://prohashing.com/api/v1/status" -UseBasicParsing -Headers $Headers).Content | ConvertFrom-Json).data +} +catch { return } + +if (-not $Request) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = "prohashing.com" +# $PriceField = "actual_last24h" +$PriceField = "estimate_current" +$DivisorMultiplier = 1 + +# + 2.9% supplementary fee for conversion +# Makes 2 + 2.9 = 4.9% +# There is 0.00015 BTC fee on withdraw as well (Estimation 0.00015/0.0025 = 6%) Using 0.0025 as most pools do use this Payout threshold +# Makes 2 + 2.9 + 6 = 10.9% !!! +# Taking 1.5 here considerinf withdraw at 0.01BTC +# $Request.$_.fees = $Request.$_.fees + 2.9 + 1.5 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { + $PoolHost = "$($HostSuffix)" + $PoolPort = $Request.$_.port + $PoolAlgorithm = Get-Algorithm $Request.$_.name + + $Divisor = $DivisorMultiplier * [Double]$Request.$_.mbtc_mh_factor + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.pps_fee)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.pps_fee)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = "$($PoolConf.UserName)" + Pass = "a=$($PoolAlgorithm),n=$($PoolConf.WorkerName.replace('ID=',''))" + Location = $Location + SSL = $false + } + } +} diff --git a/Pools/prohashing24hr.ps1 b/Pools/prohashing24hr.ps1 new file mode 100644 index 0000000..1d127ce --- /dev/null +++ b/Pools/prohashing24hr.ps1 @@ -0,0 +1,59 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { + $Headers = @{"Accept"="text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"} + $Request = ((Invoke-ProxiedWebRequest "https://prohashing.com/api/v1/status" -UseBasicParsing -Headers $Headers).Content | ConvertFrom-Json).data +} +catch { return } + +if (-not $Request) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = "prohashing.com" +# $PriceField = "actual_last24h" +$PriceField = "actual_last24h" +$DivisorMultiplier = 1 + +# + 2.9% supplementary fee for conversion +# Makes 2 + 2.9 = 4.9% +# There is 0.00015 BTC fee on withdraw as well (Estimation 0.00015/0.0025 = 6%) Using 0.0025 as most pools do use this Payout threshold +# Makes 2 + 2.9 + 6 = 10.9% !!! +# Taking 1.5 here considerinf withdraw at 0.01BTC +# $Request.$_.fees = $Request.$_.fees + 2.9 + 1.5 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { + $PoolHost = "$($HostSuffix)" + $PoolPort = $Request.$_.port + $PoolAlgorithm = Get-Algorithm $Request.$_.name + + $Divisor = $DivisorMultiplier * [Double]$Request.$_.mbtc_mh_factor + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.pps_fee)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.pps_fee)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = "$($PoolConf.UserName)" + Pass = "a=$($PoolAlgorithm),n=$($PoolConf.WorkerName.replace('ID=',''))" + Location = $Location + SSL = $false + } + } +} diff --git a/Pools/prohashingplus.ps1 b/Pools/prohashingplus.ps1 new file mode 100644 index 0000000..efe5d4f --- /dev/null +++ b/Pools/prohashingplus.ps1 @@ -0,0 +1,66 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +Try { + $dtAlgos = New-Object System.Data.DataTable + if (Test-Path ((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\prohashingPlus\prohashingPlus.xml")) { + $dtAlgos.ReadXml((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\prohashingPlus\prohashingPlus.xml") | out-null + } +} +catch { return } + +if (-not $dtAlgos) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = "prohashing.com" +$PriceField = "Plus_Price" +# $PriceField = "actual_last24h" +# $PriceField = "estimate_current" +$DivisorMultiplier = 1 + +# + 2.9% supplementary fee for conversion +# Makes 2 + 2.9 = 4.9% +# There is 0.00015 BTC fee on withdraw as well (Estimation 0.00015/0.0025 = 6%) Using 0.0025 as most pools do use this Payout threshold +# Makes 2 + 2.9 + 6 = 10.9% !!! +# $Request.$_.fees = $Request.$_.fees + 2.9 + 6 +# Taking 1.5 here considerinf withdraw at 0.01BTC + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$dtAlgos | foreach { + $PoolHost = "$($HostSuffix)" + $PoolPort = $_.port + $PoolAlgorithm = Get-Algorithm $_.algo + + $Divisor = $DivisorMultiplier * [Double]$_.mbtc_mh_factor + + $Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$_.$PriceField / $Divisor * (1 - (($_.fees)))) + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + $PoolPassword = If ( ! $Config.PartyWhenAvailable ) {"$($WorkerName),c=$($PwdCurr)"} else { "$($WorkerName),c=$($PwdCurr),m=party.NPlusMiner" } + $PoolPassword = If ( $_.symbol) { "$($PoolPassword),mc=$($_.symbol)" } else { $PoolPassword } + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = $_.symbol + Price = $Stat.Live*$PoolConf.PricePenaltyFactor #*$SoloPenalty + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = "$($PoolConf.UserName)" + Pass = "a=$($PoolAlgorithm),n=$($PoolConf.WorkerName.replace('ID=',''))" + Location = $Location + SSL = $false + Coin = "Auto ($($_.symbol))" + SoloBlocksPenalty = $_.SoloBlocksPenalty + } + } +} diff --git a/Pools/zergpool.ps1 b/Pools/zergpool.ps1 new file mode 100644 index 0000000..dbe2d16 --- /dev/null +++ b/Pools/zergpool.ps1 @@ -0,0 +1,51 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { + $Request = Invoke-ProxiedWebRequest "http://api.zergpool.com:8080/api/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json +} +catch { return } + +if (-not $Request) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = ".mine.zergpool.com" +# $PriceField = "actual_last24h" +$PriceField = "estimate_current" +$DivisorMultiplier = 1000000 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { + $PoolHost = "$($_)$($HostSuffix)" + $PoolPort = $Request.$_.port + $PoolAlgorithm = Get-Algorithm $Request.$_.name + + $Divisor = $DivisorMultiplier * [Double]$Request.$_.mbtc_mh_factor + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + } + } +} diff --git a/Pools/zergpool24hr.ps1 b/Pools/zergpool24hr.ps1 new file mode 100644 index 0000000..a19a2c4 --- /dev/null +++ b/Pools/zergpool24hr.ps1 @@ -0,0 +1,51 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +try { + $Request = Invoke-ProxiedWebRequest "http://api.zergpool.com:8080/api/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json +} +catch { return } + +if (-not $Request) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = ".mine.zergpool.com" +$PriceField = "actual_last24h" +# $PriceField = "estimate_current" +$DivisorMultiplier = 1000000000 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { + $PoolHost = "$($_)$($HostSuffix)" + $PoolPort = $Request.$_.port + $PoolAlgorithm = Get-Algorithm $Request.$_.name + + $Divisor = $DivisorMultiplier * [Double]$Request.$_.mbtc_mh_factor + + if ((Get-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit") -eq $null) {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + else {$Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100)))} + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = "" + Price = $Stat.Live*$PoolConf.PricePenaltyFactor + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = "$($WorkerName),c=$($PwdCurr)" + Location = $Location + SSL = $false + } + } +} diff --git a/Pools/zergpoolplus.ps1 b/Pools/zergpoolplus.ps1 new file mode 100644 index 0000000..99fbbf4 --- /dev/null +++ b/Pools/zergpoolplus.ps1 @@ -0,0 +1,59 @@ +if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} + +Try { + $dtAlgos = New-Object System.Data.DataTable + if (Test-Path ((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\zergpoolplus\zergpoolplus.xml")) { + $dtAlgos.ReadXml((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\zergpoolplus\zergpoolplus.xml") | out-null + } +} +catch { return } + +if (-not $dtAlgos) {return} + +$Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName +$HostSuffix = ".mine.zergpool.com" +$PriceField = "Plus_Price" +# $PriceField = "actual_last24h" +# $PriceField = "estimate_current" +$DivisorMultiplier = 1000000 + +$Location = "US" + +# Placed here for Perf (Disk reads) + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $PoolConf = $Config.PoolsConfig.$ConfName + +$dtAlgos | foreach { + $PoolHost = "$($_.algo)$($HostSuffix)" + $PoolPort = $_.port + $PoolAlgorithm = Get-Algorithm $_.algo + + $Divisor = $DivisorMultiplier * [Double]$_.mbtc_mh_factor + + $Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$_.$PriceField / $Divisor * (1 - ($_.fees / 100))) + + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + + $PoolPassword = If ( ! $Config.PartyWhenAvailable ) {"$($WorkerName),c=$($PwdCurr)"} else { "$($WorkerName),c=$($PwdCurr),m=party.NPlusMiner" } + $PoolPassword = If ( $_.symbol) { "$($PoolPassword),mc=$($_.symbol)" } else { $PoolPassword } + + if ($PoolConf.Wallet) { + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = $_.symbol + Price = $Stat.Live*$PoolConf.PricePenaltyFactor #*$SoloPenalty + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = $PoolPassword + Location = $Location + SSL = $false + Coin = $_.symbol + SoloBlocksPenalty = $_.SoloBlocksPenalty + } + } +} diff --git a/Pools/zpool.ps1 b/Pools/zpool.ps1 index 0d4ca0c..c9b358c 100644 --- a/Pools/zpool.ps1 +++ b/Pools/zpool.ps1 @@ -1,7 +1,7 @@ if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} try { - $Request = Invoke-WebRequest "http://www.zpool.ca/api/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json + $Request = Invoke-ProxiedWebRequest "http://www.zpool.ca/api/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json } catch { return } @@ -14,34 +14,34 @@ $PriceField = "estimate_current" $DivisorMultiplier = 1000000 # Placed here for Perf (Disk reads) - $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} $PoolConf = $Config.PoolsConfig.$ConfName $Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { - $Algo = $_ + $Algo = $_ $PoolHost = "$($_)$($HostSuffix)" $PoolPort = $Request.$_.port $PoolAlgorithm = Get-Algorithm $Request.$_.name $Divisor = $DivisorMultiplier * [Double]$Request.$_.mbtc_mh_factor - $Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100))) - + $Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100))) + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} - $Locations = "eu", "na", "sea" - $Locations | ForEach-Object { - $Pool_Location = $_ - - switch ($Pool_Location) { - "eu" {$Location = "EU"} - "na" {$Location = "US"} - "sea" {$Location = "JP"} - default {$Location = "US"} - } - $PoolHost = "$($Algo).$($Pool_Location)$($HostSuffix)" + $Locations = "eu", "na", "sea" + $Locations | ForEach-Object { + $Pool_Location = $_ + + switch ($Pool_Location) { + "eu" {$Location = "EU"} + "na" {$Location = "US"} + "sea" {$Location = "JP"} + default {$Location = "US"} + } + $PoolHost = "$($Algo).$($Pool_Location)$($HostSuffix)" if ($PoolConf.Wallet) { [PSCustomObject]@{ diff --git a/Pools/zpool24hr.ps1 b/Pools/zpool24hr.ps1 index f8b8703..1b4dc24 100644 --- a/Pools/zpool24hr.ps1 +++ b/Pools/zpool24hr.ps1 @@ -1,7 +1,7 @@ if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} try { - $Request = Invoke-WebRequest "http://www.zpool.ca/api/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json + $Request = Invoke-ProxiedWebRequest "http://www.zpool.ca/api/status" -UseBasicParsing -Headers @{"Cache-Control" = "no-cache"} | ConvertFrom-Json } catch { return } @@ -14,12 +14,12 @@ $PriceField = "actual_last24h" $DivisorMultiplier = 1000000000 # Placed here for Perf (Disk reads) - $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} $PoolConf = $Config.PoolsConfig.$ConfName $Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { - $Algo = $_ + $Algo = $_ $PoolHost = "$($_)$($HostSuffix)" $PoolPort = $Request.$_.port $PoolAlgorithm = Get-Algorithm $Request.$_.name @@ -32,17 +32,17 @@ $DivisorMultiplier = 1000000000 $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} - $Locations = "eu", "na", "sea" - $Locations | ForEach-Object { - $Pool_Location = $_ - - switch ($Pool_Location) { - "eu" {$Location = "EU"} - "na" {$Location = "US"} - "sea" {$Location = "JP"} - default {$Location = "US"} - } - $PoolHost = "$($Algo).$($Pool_Location)$($HostSuffix)" + $Locations = "eu", "na", "sea" + $Locations | ForEach-Object { + $Pool_Location = $_ + + switch ($Pool_Location) { + "eu" {$Location = "EU"} + "na" {$Location = "US"} + "sea" {$Location = "JP"} + default {$Location = "US"} + } + $PoolHost = "$($Algo).$($Pool_Location)$($HostSuffix)" if ($PoolConf.Wallet) { [PSCustomObject]@{ diff --git a/Pools/zpoolplus.ps1 b/Pools/zpoolplus.ps1 index 7311536..db1a442 100644 --- a/Pools/zpoolplus.ps1 +++ b/Pools/zpoolplus.ps1 @@ -1,61 +1,73 @@ if (!(IsLoaded(".\Includes\include.ps1"))) {. .\Includes\include.ps1;RegisterLoaded(".\Includes\include.ps1")} Try { - $Request = get-content ((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\zpoolplus\zpoolplus.json") | ConvertFrom-Json + $dtAlgos = New-Object System.Data.DataTable + if (Test-Path ((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\zpoolplus\zpoolplus.xml")) { + $dtAlgos.ReadXml((split-path -parent (get-item $script:MyInvocation.MyCommand.Path).Directory) + "\BrainPlus\zpoolplus\zpoolplus.xml") | out-null + } } catch { return } -if (-not $Request) {return} +if (-not $dtAlgos) {return} $Name = (Get-Item $script:MyInvocation.MyCommand.Path).BaseName $HostSuffix = ".mine.zpool.ca" -$PriceField = "actual_last24h" +$PriceField = "Plus_Price" +# $PriceField = "actual_last24h" # $PriceField = "estimate_current" $DivisorMultiplier = 1000000 + +$Location = "US" # Placed here for Perf (Disk reads) - $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} + $ConfName = if ($Config.PoolsConfig.$Name -ne $Null){$Name}else{"default"} $PoolConf = $Config.PoolsConfig.$ConfName - - - $Request | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { - $Algo = $_ - $PoolPort = $Request.$_.port - $PoolAlgorithm = Get-Algorithm $Request.$_.name - - $Divisor = $DivisorMultiplier * [Double]$Request.$_.mbtc_mh_factor - $Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Request.$_.$PriceField / $Divisor * (1 - ($Request.$_.fees / 100))) +$dtAlgos | foreach { + $Pool = $_ + $PoolHost = "$($Pool.algo)$($HostSuffix)" + $PoolPort = $Pool.port + $PoolAlgorithm = Get-Algorithm $Pool.algo + + $Divisor = $DivisorMultiplier * [Double]$Pool.mbtc_mh_factor - $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} - $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + $Stat = Set-Stat -Name "$($Name)_$($PoolAlgorithm)_Profit" -Value ([Double]$Pool.$PriceField / $Divisor * (1 - ($Pool.fees / 100))) - $Locations = "eu", "na", "sea" - $Locations | ForEach-Object { - $Pool_Location = $_ - switch ($Pool_Location) { - "eu" {$Location = "EU"} - "na" {$Location = "US"} - "sea" {$Location = "JP"} - default {$Location = "US"} - } - $PoolHost = "$($Algo).$($Pool_Location)$($HostSuffix)" + $PwdCurr = if ($PoolConf.PwdCurrency) {$PoolConf.PwdCurrency}else {$Config.Passwordcurrency} + $WorkerName = If ($PoolConf.WorkerName -like "ID=*") {$PoolConf.WorkerName} else {"ID=$($PoolConf.WorkerName)"} + $PoolPassword = If ( ! $Config.PartyWhenAvailable ) {"$($WorkerName),c=$($PwdCurr)"} else { "$($WorkerName),c=$($PwdCurr),m=party.NPlusMiner" } + $PoolPassword = If ( $Pool.symbol ) { "$($PoolPassword),zap=$($Pool.symbol)" } else { $PoolPassword } + + $Locations = "eu", "na", "sea", "jp" + $Locations | ForEach-Object { + $Pool_Location = $_ + + switch ($Pool_Location) { + "eu" {$Location = "EU"} + "na" {$Location = "US"} + "sea" {$Location = "JP"} + "jp" {$Location = "JP"} + default {$Location = "US"} + } + $PoolHost = "$($Pool.algo).$($Pool_Location)$($HostSuffix)" + if ($PoolConf.Wallet) { - [PSCustomObject]@{ - Algorithm = $PoolAlgorithm - Info = "$ahashpool_Coin $ahashpool_Coinname" - Price = $Stat.Live*$PoolConf.PricePenaltyFactor - StablePrice = $Stat.Week - MarginOfError = $Stat.Week_Fluctuation - Protocol = "stratum+tcp" - Host = $PoolHost - Port = $PoolPort - User = $PoolConf.Wallet - Pass = "$($WorkerName),c=$($PwdCurr)" - Location = $Location - SSL = $false - } + [PSCustomObject]@{ + Algorithm = $PoolAlgorithm + Info = $Pool.symbol + Price = $Stat.Live*$PoolConf.PricePenaltyFactor #*$SoloPenalty + StablePrice = $Stat.Week + MarginOfError = $Stat.Week_Fluctuation + Protocol = "stratum+tcp" + Host = $PoolHost + Port = $PoolPort + User = $PoolConf.Wallet + Pass = $PoolPassword + Location = $Location + SSL = $false + Coin = $Pool.symbol + } } } } diff --git a/README.md b/README.md index edad22b..3fbcf42 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,28 @@ -Copyright (c) 2018 MrPlus (https://github.com/MrPlusGH/NPlusMiner) +Copyright (c) 2018-2019 MrPlus (https://github.com/MrPlusGH/NPlusMiner) -Copyright (c) 2018 Nemo (https://github.com/nemosminer/NemosMiner) +Copyright (c) 2018 Nemo (https://github.com/Minerx117/NemosMiner) -![alt text](https://www.zpool.ca/images/logoz.png) - # NPlusMiner ZPool Edition +

+ +

+ + # NPlusMiner-v7.4.2 - NVIDIA | AMD | CPU - Readme Updated 2019 March 1 + Readme Updated 2020 May 19 -[![Version tag](https://img.shields.io/github/release/MRPlusGH/NPlusMiner.svg)](https://github.com/MrPlusGH/NPlusMiner/releases/latest) [![Version date tag](https://img.shields.io/github/release-date/MRPlusGH/NPlusMiner.svg)](https://github.com/MRPlusGH/NPlusMiner/releases/latest) +[![Version tag](https://img.shields.io/github/release/MRPlusGH/NPlusMiner.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAAAAACPAi4CAAACpklEQVRYw+3XO2/TUBQH8PBB+ALMrEisSBAhEbUdGMsCQlEH1KELEoK1iC7AwsbA6JCGvgRCrSBVByKaVIEqqE2dRGkepGkeJLEPPufa12/7JkWiQpzBN77H/kV/+caxIxBSU5GQ+g+EA2H1ZwClSdXVPtb3WR1ajhlQ9xffL+jHtDgwlKlKI4A9iVWibwI96naN3Z/6IdKBE5DrJiD98AVy/oDcM4EtX2AjAKgoHEj0fACewAuQmxyQCj5ALhCQd3l7U2sk4lol7cBGMLDD25J2xjwutQUbYCbwBraptY6bfU8gx/suoGoCdNRHT2Cd913ASZ0DzQRuOx4AJUhVvYFR2QA6n3D73QOg7/5S8wagw4ED3H7wAChB1Q+AYwMYvMXh1AVQgneqLzAs6QB8xiHvAlgC8AXghAFtOMThvQtgCQIAlS2kMrAMcw5ATxAAAFvKhT6kcbzjAChBBoIA9mMqVNQijjMOgBIciwBya4gZbtkBI4EIIA+23YCRIABYvnYF63o0ejOqjRcRuBSLYsWmp69i700gsBgJrWdnBZbOOZB9gnfB+NIi1kL8Mp5x4yXtvXiMnUev9kDgMnbo7pS1X0ZaRTX6UYcDA5w4SlqBCjZWVUEAWjizZQUy2PgKooBa0WZyVmANG3VhAPqYIYbAHAHfKAGIA9DQpm4jMEtA2pJADFC0e/QsAjMd7K5YEogBeI++i8BU0ZlAEICafI+AHSPB7pjAqMSA1JEjgSgAbQZIWUcCYQDuM2DTkUAceMCAZJESNMYH5hkg0d/NGowP5JdfP334XHvisicQB7QVnedPNI2JAGim9PNXYTJASetAZkIAyjpQmRQAuoTSSjcAABWLPZXSo71iBU4bWG3r1NB8ATgnbyx/F7gQXOHAWd8K/wHgN3+TQF05kYYwAAAAAElFTkSuQmCC)](https://github.com/MrPlusGH/NPlusMiner/releases/latest) [![Version date tag](https://img.shields.io/github/release-date/MRPlusGH/NPlusMiner.svg)](https://github.com/MRPlusGH/NPlusMiner/releases/latest) [![Downloads](https://img.shields.io/github/downloads/MrPlusGH/NPlusMiner/total.svg)](https://img.shields.io/github/downloads/MrPlusGH/NPlusMiner/total.svg) [![GitHub license](https://img.shields.io/github/license/MRPlusGH/NPlusMiner.svg)](https://github.com/MRPlusGH/NPlusMiner/blob/master/LICENSE) -[![Discord tag](https://img.shields.io/discord/522832112311599124.svg?label=Discord&style=plastic)](https://discord.gg/2BCqPxe) [Click to Join Discord](https://discord.gg/2BCqPxe) +[![Discord tag](https://img.shields.io/discord/522832112311599124.svg?label=Discord&style=popout&logo=discord)](https://discord.gg/2BCqPxe) + +[Click to Join Discord](https://discord.gg/2BCqPxe) + +

+ +

+

+ +

***** **Have questions? Need help?** We're on Discord: https://discord.gg/2BCqPxe @@ -18,17 +30,233 @@ Copyright (c) 2018 Nemo (https://github.com/nemosminer/NemosMiner) **For more help and HowTos please check our wiki here** : https://github.com/MrPlusGH/NPlusMiner-Documentation/wiki BitcoinTalk : https://bitcointalk.org/index.php?topic=2965976.0 ***** +NPlusMiner Monitors mining pools in real-time in order to find the most profitable Algo + + GUI and easy configuration + Auto Benchmarks Each algo to get optimal speeds + Fully automated + Auto Downloads Miners + Tracks and display earnings accross pools + AutoUpdate + Monitoring + +***** -For details on NPlusMiner please refer to https://github.com/MrPlusGH/NPlusMiner/blob/master/README.md -![alt text](https://raw.githubusercontent.com/MrPlusGH/NPlusMiner-ZPool-Edition/master/Utils/screenshot.jpg) +Easy configuration, easy start in two steps: + + Run NPLusMiner + + 1. Enter your BTC address and hit Save Config + 2. Hit "Start" +Fee: + + There is a 16 minutes per day fee (1%). + Devs are doing their best to make NPlusminer your best tool. + We have a fair fee distribution system to contributors. ***** -Any donations will be much appreciated: +## Features list + + AI + + NPlusMiner provides deep data analysis to lead to the best mining decisions. + BrainPlus is the Core brain computing these calculations and criteria. + Not only this does analyze prices, but aglos/coins performances or orphans rate as well. + + GUI + + Since version 2.0 NPlusMiner has a GUI making it easy to configure and run. + Relies on config files. No need to edit bat files. Simply run NPlusMiner + Set your wallet address and hit start + For console lovers. Run NPlusMiner-ConsoleUp. + + AutoUpdate + + Since version 4.0 NPlusMiner integrates an AutoUpdate feature. + + Auto Ban miners + + There are cases where some miners might fail in some systems. + I such cases, NPlusMiner will ignore this miner after a count of failure. + Default value for max failure is 3 and can be changes in Config.json. + "MaxMinerFailure": 3 - set to 0 to deactivate autoban. + Bans are only valid for a session. NPlusMiner will retry the miner on restart. + + Pause Mining + + Ability to pause miners while keeping other jobs running (pause button) + This will stop mining activity + BrainPlus will still run in the background avoiding the learning phase on resume + EarningTracker will still run in the background avoiding the learning phase on resume + + prerun + + Ability to run a batch prior switching to a specific algo. + For example, can be used to set per algo OC via nvidiaInspector + Simply create a file named .bat in prerun folder + If .bat does not exist, will try to launch prerun/default.bat + Use overclock with caution + + Per pools config (Advanced) + + - **This is for advanced users. Do not use if you do not know what you are doing.** + - You can now set specific options per pool. For example, you can mine NiceHash on the internal wallet and other pools on a valid wallet. This configuration is provided as an example in Config\PoolsConfig-NHInternal.json + - Available options + - Wallet = your wallet address + - UserName = your MPH user name + - WorkerName = your worker name + - PricePenaltyFactor = See explanation below + - Algorithm = List of included or excluded Aglo on pool (see example files) + + - Usage + - The file Config\PoolsConfig.json contains per pool configuration details. If a pool is listed in this file, + the specific settings will be taken into account. If not, the setting for the entry name default will be used. + **Do not delete the default entry.** + - Edit Config\PoolsConfig.json + - Add an entry for the pool you want to customize + - The name must be the NPlusMiner name for the pool. ie. for ahashpool, if you use Plus. The name is ahashpoolplus. + - (**careful with json formating ;)**) + - Best way is to duplicate the default entry + - Note that the GUI only updates the default entry. Any other changes need to be done manualy + + PricePenaltyFactor (Advanced) + + - When using advanced per pool configuration, it is possible to add a penalty factor for a specific pool. This simply adds as a multiplicator on estimations presented by the pool. + - Example scenario + - NiceHash as a 4% fee - Set PricePenaltyFactor to 0.96 (1-0.04) + - You feel like a pool is exaggerating his estimations by 10% - Set PricePenaltyFactor to 0.9 + + BrainPlus - ahashpoolplus / zergpoolplus / zpoolplus / blazepoolplus / BlockMastersPlus / PhiPhiPoolPlus / StarPoolPlus / HashRefineryPlus + + Did we say AI ;) + Uses calculations based on 24hractual and currentestimate ahashpool prices to get more realistic estimate. + Includes some trust index based on past 1hr currentestimate variation from 24hr. + AND is NOT sensible to spikes. + This shows less switching than following Current Estimate and more switching that following the 24hr Actual. + Better profitability. + + Pools variants + + 24hr - uses last 24hour Actual API too request profit + -Low switching rate + plus - uses advanced calculations to maximize profit (AI) + -**Best switching rate** + normal - uses current estimate API too request profit + -High switching rate + + Developers and Contributors fee distribution + + There is a 16 minutes per day fee (1%) + + We use a fair fee distribution to developers and contributors. Fees are distibuted randomly + to a public list of devs which can be found here: http://tiny.cc/r355qy + + We want to stay completely transparent on the way fees are managed in the product. + Fees cycle occurs once every 24 hours for the selected amount of time (8 minutes). + The first donation sequence occurs 1 hour after miners are started. + If Interval is set higher than the donation time, the interval will prime. + Example for default parameters: + Miners started at 10, First donation cycle runs at 11 untill 11:16, Next donation cycle occurs 24 hours after. + All donation time and addresses are recorded in the logs folder. + + Miners Monitoring + + Keep tabs on all your mining rigs from one place + **Thanks to @NemosMiner for giving is aggreement to share the NemosMiner monitoring servers.** + You can now optionally monitor all your workers remotely, both in the GUI and via https://nemosminer.com + Monitoring setup instructions https://nemosminer.com/setup.php + + NPlusMiner does not send any personnal informations to servers. Only miner related info are collected as miner names and hashrates. Miners path are all expressed relative so we have no risk to send any personnal informations like username. -nemo = 1QGADhdMRpp9Pk5u5zG1TrHKRrdK5R81TE + Algo selection + + Users might use the Algo list in config to Include or Exclude algos. + The list simply works with a +/- system. + + +algo for algo selection + -algo for algo removal + + If "+" Used, all selected algo have to be listed + If "Minus" Used, all algo selected but exluded ones. + + Do not combine + and - for the same algo + + Examples: + Mine anything but x16r: Algo list = -x16r + Mine anything but x16r and bcd: Algo list = -x16r,-bcd + Mine only x16r: Algo list = +x16r + Mine only x16r and BCD: Algo list = +x16r,+bcd + Mine any available algo at pool: Algo list = + + Earnings Tracking + + Graphical displays BTC/H and BTC/D as well a estimation of when the pool payment threshold will be reached. + Supported pools: + ahashpool + zergpool + zpool + nicehash + miningpoolhub (partial) + Blazepool + BlockMasters + PhiPhipool + Starpool + HashRefinery + If mining more that one pools, shows stats for any supported pool + Press key e in the console window to show/hide earnings + + Support running multiple instances + + **Experimental** + More than one instance of NPlusMiner can run on the same rig + Each instance must be placed in it's own directory + Miner has to be started prior the launch of the next instance + + Optional miners (Advanced) + + Some miners are not enabled by default in NPlusMiner for a variety of reasons + A new folder can be found called "OptionalMiners" containing .ps1 files for some miners + For advanced users, refer to OptionalMiners\Readme.txt on how to use + + Algo switching log + + Simple algo switching log in csv switching.log file found in Logs folder. + You can easily track switching rate. + + Console Display Options + + Use -UIStyle Light or -UIStyle Full in config.json + Full = Usual display + Light = Show only currently mining info (Default) + UIStyle automaticaly swtiches to Full during benchmarking. + + In session console display toggle + + Press key s in the window to switch between light and full display + Press key e in the window to show/hide earnings + Will toggle display at next refresh + +***** + + +If you have Windows 8, or 8.1, please update PowerShell: +https://www.microsoft.com/en-us/download/details.aspx?id=50395 + +Some miners may need 'Visual C++ 2013' if you don't already have it: (install both x86 & x64) Visual C++ Redistributable for Visual Studio 2012/2013: https://www.microsoft.com/en-US/download/details.aspx?id=40784 + +Some miners may need 'Visual C++ 2015' if you don't already have it: (install both x86 & x64) Visual C++ Redistributable for Visual Studio 2014/2015: https://www.microsoft.com/en-US/download/details.aspx?id=48145 + +Some miners may need 'Visual C++ 2015 update 3' if you don't already have it: (install both x86 & x64) Visual C++ Redistributable for Visual Studio 2015 update 3: https://www.microsoft.com/en-us/download/details.aspx?id=53587 + +running multiple cards its recommended to increase Virtual Memory 64gb is optimal + +Requires Nvidia driver 431.86: https://www.geforce.com/drivers + +Made For & Tested with 6x1070 6x1070ti 6x1080 6x1080ti 9x1660ti 6x2060 6x2070 6x2080 6x2080ti(users have reported up to 12cards working have not tested myself) Some miners do not support more that 9 cards + +***** -MrPlus = 134bw4oTorEJUUVFhokDQDfNqTs7rBMNYy Licensed under the GNU General Public License v3.0 Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. https://github.com/mrplusgh/NPlusMiner/blob/master/LICENSE diff --git a/Utils/API-Mine.ps1 b/Utils/API-Mine.ps1 new file mode 100644 index 0000000..3f518ff --- /dev/null +++ b/Utils/API-Mine.ps1 @@ -0,0 +1,14 @@ + +Write-Host "Pausing NPlusMiner via API" + +. .\Includes\Include.ps1 + +$Variables = [hashtable]::Synchronized(@{}) +$ConfigFile = ".\Config\Config.json" +$Config = Load-Config -ConfigFile $ConfigFile + +$ServerPasswd = ConvertTo-SecureString $Config.Server_Password -AsPlainText -Force +$ServerCreds = New-Object System.Management.Automation.PSCredential ($Config.Server_User, $ServerPasswd) + +Invoke-WebRequest "http://127.0.0.1:$($Config.Server_Port)/Cmd-Mine" -Credential $ServerCreds + diff --git a/Utils/API-Pause.ps1 b/Utils/API-Pause.ps1 new file mode 100644 index 0000000..522135c --- /dev/null +++ b/Utils/API-Pause.ps1 @@ -0,0 +1,14 @@ + +Write-Host "Sending Pause command to NPlusMiner via API" + +. .\Includes\Include.ps1 + +$Variables = [hashtable]::Synchronized(@{}) +$ConfigFile = ".\Config\Config.json" +$Config = Load-Config -ConfigFile $ConfigFile + +$ServerPasswd = ConvertTo-SecureString $Config.Server_Password -AsPlainText -Force +$ServerCreds = New-Object System.Management.Automation.PSCredential ($Config.Server_User, $ServerPasswd) + +Invoke-WebRequest "http://127.0.0.1:$($Config.Server_Port)/Cmd-Pause" -Credential $ServerCreds + diff --git a/Utils/WebUI.png b/Utils/WebUI.png new file mode 100644 index 0000000000000000000000000000000000000000..da67354f3a33693013298fff648653cc85f039c3 GIT binary patch literal 89875 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