forked from MultiPoolMiner/MultiPoolMiner
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMultiPoolMiner.ps1
1207 lines (1098 loc) · 90.6 KB
/
MultiPoolMiner.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using module .\Include.psm1
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[Alias("BTC")]
[String]$Wallet,
[Parameter(Mandatory = $false)]
[Alias("User")]
[String]$UserName,
[Parameter(Mandatory = $false)]
[Alias("Worker")]
[String]$WorkerName = "multipoolminer",
[Parameter(Mandatory = $false)]
[Int]$API_ID = 0,
[Parameter(Mandatory = $false)]
[String]$API_Key = "",
[Parameter(Mandatory = $false)]
[Int]$Interval = 60, #seconds of main MPM loop (getting stats, getting miners info, calulating best miners, gethering hashrates, saving hashrates)
[Parameter(Mandatory = $false)]
[Alias("Location")]
[String]$Region = "europe", #europe/us/asia
[Parameter(Mandatory = $false)]
[Switch]$SSL = $false,
[Parameter(Mandatory = $false)]
[Alias("Device", "Type")]
[String[]]$DeviceName = @(), #i.e. CPU, GPU, GPU#02, AMD, NVIDIA, AMD#02, OpenCL#03#02 etc.
[Parameter(Mandatory = $false)]
[String[]]$Currency = ("BTC", "USD"), #i.e. GBP, EUR, ZEC, ETH etc., the first currency listed will be used as base currency for profit calculations
[Parameter(Mandatory = $false)]
[String[]]$ExcludeDeviceName = @(), #i.e. CPU, GPU, GPU#02, AMD, NVIDIA, AMD#02, OpenCL#03#02 etc. will not be used for mining
[Parameter(Mandatory = $false)]
[String[]]$Algorithm = @(), #i.e. Ethash, Equihash, CryptonightV7 etc.
[Parameter(Mandatory = $false)]
[String[]]$CoinName = @(), #i.e. Monero, Zcash etc.
[Parameter(Mandatory = $false)]
[String[]]$CurrencySymbol = @(), #i.e. LUX, XVG etc.
[Parameter(Mandatory = $false)]
[Alias("Miner")]
[String[]]$MinerName = @(),
[Parameter(Mandatory = $false)]
[Alias("Pool")]
[String[]]$PoolName = @(),
[Parameter(Mandatory = $false)]
[String[]]$ExcludeAlgorithm = @(), #i.e. Ethash, Equihash, CryptonightV7 etc.
[Parameter(Mandatory = $false)]
[String[]]$ExcludeCoinName = @(), #i.e. Monero, Zcash etc.
[Parameter(Mandatory = $false)]
[String[]]$ExcludeCurrencySymbol = @(), #i.e. LUX, XVG etc.
[Parameter(Mandatory = $false)]
[Alias("ExcludeMiner")]
[String[]]$ExcludeMinerName = @(),
[Parameter(Mandatory = $false)]
[Alias("ExcludePool")]
[String[]]$ExcludePoolName = @(),
[Parameter(Mandatory = $false)]
[Alias("DisableDualMining")]
[Switch]$SingleAlgoMining = $false, #disables all dual mining miners
[Parameter(Mandatory = $false)]
[ValidateRange(10, 1440)]
[Int]$Donate = 24, #Minutes per Day, Allowed values: 10 - 1440
[Parameter(Mandatory = $false)]
[String]$Proxy = "", #i.e http://192.0.0.1:8080
[Parameter(Mandatory = $false)]
[ValidateRange(0, 10)]
[Int]$Delay = 0, #seconds before opening each miner. Allowed values: 0 - 10
[Parameter(Mandatory = $false)]
[Switch]$Watchdog = $false,
[Parameter(Mandatory = $false)]
[Alias("Uri", "Url")]
[String]$MinerStatusUrl = "", #i.e https://multipoolminer.io/monitor/miner.php
[Parameter(Mandatory = $false)]
[String]$MinerStatusKey = "",
[Parameter(Mandatory = $false)]
[ValidateRange(30, 300)]
[Double]$ReportStatusInterval = 90, #seconds until next miner status update. Allowed values 30 - 300, 0 to disable
[Parameter(Mandatory = $false)]
[Double]$SwitchingPrevention = 1, #zero does not prevent miners switching
[Parameter(Mandatory = $false)]
[ValidateRange(0, 1)]
[Double]$MinAccuracy = 0.5, #Only pools with price accuracy greater than the configured value. Allowed values: 0.0 - 1.0 (0% - 100%)
[Parameter(Mandatory = $false)]
[Switch]$ShowMinerWindow = $false, #if true most miner windows will be visible (they can steal focus) - miners that use the 'Wrapper' API will still remain hidden
[Parameter(Mandatory = $false)]
[Switch]$ShowAllMiners = $false, #Use only use fastest miner per algo and device index. E.g. if there are 2 miners available to mine the same algo, only the faster of the two will ever be used, the slower ones will also be hidden in the summary screen
[Parameter(Mandatory = $false)]
[Switch]$IgnoreFees = $false, #if $true MPM will ignore miner and pool fees for its calculations (as older versions did)
[Parameter(Mandatory = $false)]
[ValidateRange(1, 999)]
[Int]$PoolBalancesUpdateInterval = 15, #MPM will force update balances every n minutes to limit pool API requests (but never more than ONCE per loop). Allowed values 1 - 999 minutes
[Parameter(Mandatory = $false)]
[Switch]$DisableDeviceDetection = $false, #if true MPM won't create separate miner instances per device model. This will decrease profitability.
[Parameter(Mandatory = $false)]
[String]$ConfigFile = ".\Config.txt", #default config file
[ValidateRange(5, 20)]
[Int]$HashRateSamplesPerInterval = 10, #approx number of hashrate samples that MPM will collect per interval (higher numbers produce more exact numbers, but use more CPU cycles and memory). Allowed values: 5 - 20
[Parameter(Mandatory = $false)]
[ValidateRange(60, 300)]
[Int]$BenchmarkInterval = 60, #seconds per loop that MPM will have to collect hashrates when benchmarking. Allowed values: 60 - 300
[Parameter(Mandatory = $false)]
[ValidateRange(10, 30)]
[Int]$MinHashRateSamples = 10, #minumum number of hashrate samples that MPM will collect in benchmark operation (higher numbers produce more exact numbers, but will prolongue benchmarking. Allowed values: 10 - 30
[Parameter(Mandatory = $false)]
[ValidateRange(0.0, 1.0)]
[Double]$PricePenaltyFactor = 1, #Estimated profit as projected by pool will be multiplied by this facator. Allowed values: 0.0 - 1.0
[Parameter(Mandatory = $false)]
[Switch]$MeasurePowerUsage = $false, #if true MPM will gather power usage per device and calculate power costs
[Parameter(Mandatory = $false)]
[Hashtable]$PowerPrices = @{ }, #Power price per KW, set value for each time frame, e.g. "00:00"=0.3;"06:30"=0.6;"18:30"=0.3, 24hr format!
[Parameter(Mandatory = $false)]
[Switch]$ShowPowerUsage = $false, #Show power usage in miner overview list
[Parameter(Mandatory = $false)]
[ValidateRange(0, 999)]
[Float]$BasePowerUsage = 0, #Additional base power usage (in Watt) for running the computer, monitor etc. regardless of mining hardware. Allowed values: 0.0 - 999
[Parameter(Mandatory = $false)]
[Switch]$IgnorePowerCost = $false, #if true MPM will ignore the calculated power costs when calculating profit
[Parameter(Mandatory = $false)]
[ValidateRange(0, 999)]
[Float]$ProfitabilityThreshold = 0, #Minimum profit (in $Currency[0]) that must be made otherwise all mining will stop, set to 0 to allow mining even when making losses. Allowed values: 0.0 - 999
[Parameter(Mandatory = $false)]
[ValidateRange(0, 60)]
[Int]$WarmupTime = 30, #Time the miner are allowed to warm up, e.g. to compile the binaries or to get the APi reads before it get marked as failed. Default 30 (seconds).
[Parameter(Mandatory = $false)]
[PSCustomObject]$HWiNFO64_SensorMapping, #custom HWiNFO64 sensor mapping, only required when $MeasurePowerUsage is $true, see ConfigHWinfo64.pdf
[Parameter(Mandatory = $false)]
[PSCustomObject]$MinWorker = [PSCustomObject]@{"*" = 10 }, #One entry per Algorithm name (wildcards like * and ? are supported) and workers that must be available for the algorithm, low number of workers is similar to solo mining :-(. Default for all algorithms is 10. Note: Wildcards (* and ?) for the algorithm names are supported. If an algorithm name/wildcard matches more than one entry then the lower number takes priority.
[Parameter(Mandatory = $false)]
[ValidateRange(0, 100)]
[Float]$AllowedBadShareRatio = 0, #Allowed ratio of bad shares (total / bad) as reported by the miner. If the ratio exceeds the configured threshold then the miner will marked as failed. Allowed values: 0.00 - 1.00. Default of 0 disables this check
[Parameter(Mandatory = $false)]
[Int]$APIPort = 3999, #Port for the MPM API. The miner port range will start from $APIPort +1. Default: 3999,
[Parameter(Mandatory = $false)]
[Switch]$ShowAllPoolBalances, #Include this command to display the balances of all pools (including those that are excluded with '-ExcludePoolName') on the summary screen and in the web dashboard.
[Parameter(Mandatory = $false)]
[Switch]$Dashboard = $false, #If true launch dashboard
[Parameter(Mandatory = $false)]
[Switch]$DisableMinersWithDevFee = $false, #Use only miners that do not have a dev fee built in
[Parameter(Mandatory = $false)]
[Switch]$DisableDevFeeMining = $false, #Set to true to disable miner fees (Note: not all miners support turning off their built in fees, others will reduce the hashrate)
[Parameter(Mandatory = $false)]
[Switch]$DisableEstimateCorrection = $false, #If true MPM will reduce the algo price by a correction factor (actual_last24h / estimate_last24h) to counter pool overestimated prices
[Parameter(Mandatory = $false)]
[PSCustomObject]$IntervalMultiplier = [PSCustomObject]@{"EquihashR15053" = 2; "Mtp" = 2; "MtpNicehash" = 2; "ProgPow" = 2; "Rfv2" = 2; "X16r" = 5; "X16Rt" = 3; "X16RtGin" = 3; "X16RtVeil" = 3 } #IntervalMultiplier per Algo, if algo is not listed the default of 1 is used
)
Clear-Host
$Version = "3.5.5.1"
$VersionCompatibility = "3.3.0"
$Strikes = 3
$SyncWindow = 5 #minutes
$ProgressPreference = "silentlyContinue"
Set-Location (Split-Path $MyInvocation.MyCommand.Path)
Import-Module NetSecurity -ErrorAction Ignore
Import-Module Defender -ErrorAction Ignore
Import-Module "$env:Windir\System32\WindowsPowerShell\v1.0\Modules\NetSecurity\NetSecurity.psd1" -ErrorAction Ignore
Import-Module "$env:Windir\System32\WindowsPowerShell\v1.0\Modules\Defender\Defender.psd1" -ErrorAction Ignore
if (Get-Command "Start-ThreadJob" -ErrorAction SilentlyContinue) { Set-Alias Start-Job Start-ThreadJob }
$Algorithm = [String[]]@($Algorithm | ForEach-Object { @(@(Get-Algorithm ($_ -split '-' | Select-Object -Index 0) | Select-Object) + @($_ -split '-' | Select-Object -Skip 1) | Select-Object -Unique) -join '-' } | Select-Object)
$ExcludeAlgorithm = [String[]]@($ExcludeAlgorithm | ForEach-Object { @(@(Get-Algorithm ($_ -split '-' | Select-Object -Index 0) | Select-Object) + @($_ -split '-' | Select-Object -Skip 1) | Select-Object -Unique) -join '-' } | Select-Object)
$Region = [String]@($Region | ForEach-Object { Get-Region $_ } | Select-Object -Index 0)
$Currency = [String[]]@($Currency | ForEach-Object { $_.ToUpper() } | Select-Object)
$Timer = (Get-Date).ToUniversalTime()
$StatEnd = $Timer
$DecayStart = $Timer
$DecayPeriod = 60 #seconds
$DecayBase = 1 - 0.1 #decimal percentage
<#legacy#>$Intervals = @()
$WatchdogTimers = @()
[Miner[]]$ActiveMiners = @()
<#legacy#>$RunningMiners = @()
<#legacy#>$AllMinerPaths = @()
<#legacy#>$NewPools_JobsDurations = @()
#Start the log
Start-Transcript ".\Logs\MultiPoolMiner_$(Get-Date -Format "yyyy-MM-dd_HH-mm-ss").txt"
Write-Log "Starting MultiPoolMiner® v$Version © 2017-$((Get-Date).Year) MultiPoolMiner.io"
#Unblock files
if (Get-Command "Unblock-File" -ErrorAction Ignore) { Get-ChildItem . -Recurse | Unblock-File }
if ((Get-Command "Get-MpPreference" -ErrorAction Ignore) -and (Get-MpComputerStatus -ErrorAction Ignore) -and (Get-MpPreference).ExclusionPath -notcontains (Convert-Path .)) {
Start-Process (@{desktop = "powershell"; core = "pwsh" }.$PSEdition) "-Command Import-Module '$env:Windir\System32\WindowsPowerShell\v1.0\Modules\Defender\Defender.psd1'; Add-MpPreference -ExclusionPath '$(Convert-Path .)'" -Verb runAs
}
#Initialize the API
if (Test-Path .\API.psm1 -PathType Leaf -ErrorAction Ignore) { Import-Module .\API.psm1 }
#Initialize config file
if (-not [IO.Path]::GetExtension($ConfigFile)) { $ConfigFile = "$($ConfigFile).txt" }
$Config_Temp = [PSCustomObject]@{ }
[Hashtable]$Config_Parameters = @{ }
$MyInvocation.MyCommand.Parameters.Keys | Sort-Object | ForEach-Object {
$Config_Parameters.$_ = Get-Variable $_ -ValueOnly -ErrorAction SilentlyContinue
if ($Config_Parameters.$_ -is [Switch]) { $Config_Parameters.$_ = [Boolean]$Config_Parameters.$_ }
$Config_Temp | Add-Member @{$_ = "`$$_" }
}
$Config_Temp | Add-Member @{Pools = @{ } } -Force
$Config_Temp | Add-Member @{MinersLegacy = @{ } } -Force
$Config_Temp | Add-Member @{Wallets = @{BTC = "`$Wallet" } } -Force
$Config_Temp | Add-Member @{VersionCompatibility = $VersionCompatibility } -Force
if (-not (Test-Path $ConfigFile -PathType Leaf -ErrorAction Ignore)) {
Write-Log -Level Info -Message "No valid config file found. Creating new config file ($ConfigFile) using defaults. "
$Config_Temp | ConvertTo-Json -Depth 10 | Set-Content $ConfigFile
}
Remove-Variable Config_Temp
$Config = [PSCustomObject]@{ }
#Set donation parameters
$LastDonated = $Timer.AddDays(-1).AddHours(1)
$WalletDonate = ((@("1Q24z7gHPDbedkaWDTFqhMF8g7iHMehsCb") * 3) + (@("16Qf1mEk5x2WjJ1HhfnvPnqQEi2fvCeity") * 2) + (@("1GPSq8txFnyrYdXL8t6S94mYdF8cGqVQJF") * 2))[(Get-Random -Minimum 0 -Maximum ((3 + 2 + 2) - 1))]
$UserNameDonate = ((@("aaronsace") * 3) + (@("grantemsley") * 2) + (@("uselessguru") * 2))[(Get-Random -Minimum 0 -Maximum ((3 + 2 + 2) - 1))]
$WorkerNameDonate = "multipoolminer_donate_$Version" -replace '[\W]', '-'
#Set process priority to BelowNormal to avoid hash rate drops on systems with weak CPUs
(Get-Process -Id $PID).PriorityClass = "BelowNormal"
#HWiNFO64 ready? If HWiNFO64 is running it will recreate the reg key automatically
if (Test-Path "HKCU:\Software\HWiNFO64\VSB") { Remove-Item -Path "HKCU:\Software\HWiNFO64\VSB" -Recurse -ErrorAction SilentlyContinue }
if (Test-Path "APIs" -PathType Container -ErrorAction Ignore) { Get-ChildItem "APIs" -File | ForEach-Object { . $_.FullName } }
while (-not $API.Stop) {
#Display downloader progress
if ($Downloader) { $Downloader | Receive-Job }
#Load the configuration
$OldConfig = $Config | ConvertTo-Json -Depth 10 | ConvertFrom-Json
$Config = Get-ChildItemContent $ConfigFile -Parameters $Config_Parameters | Select-Object -ExpandProperty Content
if ($Config -isnot [PSCustomObject]) {
Write-Log -Level Warn "Config file ($ConfigFile) is corrupt. "
$Config = [PSCustomObject]@{ }
}
if ($Config.VersionCompatibility -and $VersionCompatibility -and [System.Version]$Config.VersionCompatibility -lt [System.Version]$VersionCompatibility) {
Write-Log -Level Warn "Config file ($ConfigFile [Version $($Config.VersionCompatibility)]) is not a valid configuration file (min. required config file version is $VersionCompatibility). "
}
#Repair the configuration
$Config | Add-Member $Config_Parameters -ErrorAction Ignore
$Config | Add-Member Wallets ([PSCustomObject]@{ }) -ErrorAction Ignore
if ($Wallet -and -not $Config.Wallets.BTC) { $Config.Wallets | Add-Member BTC $Config.Wallet -ErrorAction Ignore }
if (-not $Config.MinerStatusKey -and $Config.Wallets.BTC) { $Config | Add-Member MinerStatusKey $Config.Wallets.BTC -Force } #for backward compatibility
$Config | Add-Member Pools ([PSCustomObject]@{ }) -ErrorAction Ignore
@(Get-ChildItem "Pools" -File -ErrorAction Ignore) + @(Get-ChildItem "Balances" -File -ErrorAction Ignore) | Select-Object -ExpandProperty BaseName -Unique | ForEach-Object {
$Config.Pools | Add-Member $_ ([PSCustomObject]@{ }) -ErrorAction Ignore
$Config.Pools.$_ | Add-Member User $Config.UserName -ErrorAction Ignore
$Config.Pools.$_ | Add-Member Worker $Config.WorkerName -ErrorAction Ignore
$Config.Pools.$_ | Add-Member Wallets $Config.Wallets -ErrorAction Ignore
$Config.Pools.$_ | Add-Member API_ID $Config.API_ID -ErrorAction Ignore
$Config.Pools.$_ | Add-Member API_Key $Config.API_Key -ErrorAction Ignore
$Config.Pools.$_ | Add-Member PricePenaltyFactor $Config.PricePenaltyFactor -ErrorAction Ignore
$Config.Pools.$_ | Add-Member DisableEstimateCorrection $Config.DisableEstimateCorrection -ErrorAction Ignore
}
$Config | Add-Member Miners (Get-ChildItemContent "Miners") -ErrorAction Ignore
$Config | Add-Member MinersLegacy ([PSCustomObject]@{ }) -ErrorAction Ignore
Get-ChildItem "MinersLegacy" -File -ErrorAction Ignore | Select-Object -ExpandProperty BaseName | ForEach-Object {
$Config.MinersLegacy | Add-Member ($_ -split '-' | Select-Object -Index 0) ([PSCustomObject]@{ }) -ErrorAction Ignore
}
$BackupConfig = $Config | ConvertTo-Json -Depth 10 | ConvertFrom-Json
#Apply the configuration
$FirstCurrency = $($Config.Currency | Select-Object -Index 0)
if ($Config.Proxy) { $PSDefaultParameterValues["*:Proxy"] = $Config.Proxy }
else { $PSDefaultParameterValues.Remove("*:Proxy") }
#Needs clean-up
if ($API.Port -and $Config.APIPort -ne $API.Port) {
#API port has changed, stop API and miners
Write-Log -Level Info "Port for web dashboard and API has changed ($($API.Port) -> $($Config.APIPort)). $(if ($ActiveMiners | Where-Object Best) { "Stopping all runnig miners. " })"
$RunningMiners | ForEach-Object {
$Miner = $_
Write-Log "Stopping miner ($($Miner.Name) {$(($Miner.Algorithm | ForEach-Object {"$($_)@$($Pools.$_.Name)"}) -join "; ")}). "
$Miner.SetStatus("Idle")
$Miner.StatusMessage = " stopped gracefully (initiated by API port change)"
$RunningMiners = @($RunningMiners | Where-Object { $_ -ne $Miner })
}
Get-CIMInstance CIM_Process | Where-Object ExecutablePath | Where-Object { $AllMinerPaths -contains $_.ExecutablePath } | Select-Object -ExpandProperty ProcessID | ForEach-Object { Stop-Process -Id $_ -Force -ErrorAction Ignore }
try {
Invoke-WebRequest -Uri "http://localhost:$($API.Port)/stopapi" -Timeout 1 -ErrorAction SilentlyContinue | Out-Null
}
catch { }
Remove-Variable API -ErrorAction SilentlyContinue
$ReportStatusJob | Select-Object | Remove-Job -Force
$ReportStatusJob = $null
}
#Needs clean-up
if ($Config.APIPort -and (-not $API.Port)) {
$TCPClient = New-Object System.Net.Sockets.TCPClient
$AsyncResult = $TCPClient.BeginConnect("localhost", $Config.APIPort, $null, $null)
if ($AsyncResult.AsyncWaitHandle.WaitOne(100)) {
Write-Log -Level Error "Error starting web dashboard and API on port $($Config.APIPort). Port is in use. "
try { $Null = $TCPClient.EndConnect($AsyncResult) }
catch { }
}
else {
#Start API server
if ($API) { Remove-Variable API }
$Global:Stats = [PSCustomObject]@{ } #temp fix
Start-APIServer -Port $Config.APIPort
if ($API.Port) {
Write-Log -Level Info "Web dashboard and API (version $($API.APIVersion)) running on http://localhost:$($API.Port). "
if ($Config.Dashboard) { Start-Process "http://localhost:$($Config.APIPort)/" } # Start web dashboard
}
else {
Write-Log -Level Error "Error starting web dashboard and API on port $($Config.APIPort). "
$API = @{ }
}
}
Remove-Variable AsyncResult
Remove-Variable TCPClient
}
if ($API) {
$API.Version = [PSCustomObject]@{"Core" = $Version; "API" = $API.APIVersion } #Give API access to the current version
$API.Config = $BackupConfig #Give API access to the current running configuration
}
if ($API.Port -and $Config.MinerStatusKey -and $Config.ReportStatusInterval -and (-not $ReportStatusJob)) {
$ReportStatusJob = Start-Job -Name "ReportStatus" -InitializationScript ([scriptblock]::Create("Set-Location('$(Get-Location)')")) -ArgumentList "http://localhost:$($API.Port)" -FilePath .\ReportStatus.ps1 #Start monitoring service (requires running API)
}
#Load unprofitable algorithms
if (Test-Path ".\UnprofitableAlgorithms.txt" -PathType Leaf -ErrorAction Ignore) {
$UnprofitableAlgorithms = [String[]](Get-Content ".\UnprofitableAlgorithms.txt" | ConvertFrom-Json -ErrorAction SilentlyContinue | Sort-Object -Unique)
if ($API) { $API.UnprofitableAlgorithms = $UnprofitableAlgorithms }
}
#Activate or deactivate donation
if ($Config.Donate -lt 10) { $Config.Donate = 10 }
if ($Timer.AddDays(-1).AddMinutes(-1).AddSeconds(1) -ge $LastDonated) { $LastDonated = $Timer }
if ($Timer.AddDays(-1).AddMinutes($Config.Donate) -ge $LastDonated) {
if ($WalletDonate -and $UserNameDonate -and $WorkerNameDonate) {
Write-Log "Donation run, mining to donation address for the next $(($LastDonated - ($Timer.AddDays(-1))).Minutes +1) minutes. Note: MPM will use ALL available pools. "
$Config | Add-Member Pools ([PSCustomObject]@{ }) -Force
Get-ChildItem "Pools" -File -ErrorAction Ignore | Select-Object -ExpandProperty BaseName | ForEach-Object {
$Config.Pools | Add-Member $_ (
[PSCustomObject]@{
User = $UserNameDonate
Worker = $WorkerNameDonate
Wallets = [PSCustomObject]@{ BTC = $WalletDonate }
PricePenaltyFactor = 0
}
) -Force
}
$Config | Add-Member PoolName (@()) -Force
$Config | Add-Member ExcludePoolName (@()) -Force
}
else {
Write-Log -Level Warn "Donation information is missing. "
}
}
else {
Write-Log ("Mining for you. Donation run will start in {0:hh} hour(s) {0:mm} minute(s). " -f $($LastDonated.AddDays(1) - ($Timer.AddMinutes($Config.Donate))))
}
#Check if the configuration has changed
if (($OldConfig | ConvertTo-Json -Compress -Depth 10) -ne ($Config | ConvertTo-Json -Compress -Depth 10)) {
if ($AllDevices) { Write-Log -Level Info "Config change detected. " }
$AllPools | Select-Object | ForEach-Object { $_.Price = 0 }
$AllDevices = @(Get-Device -Refresh | Select-Object)
}
if ($API) { $API.AllDevices = $AllDevices } #Give API access to the device information
#Load information about the devices
$Devices = @(Get-Device -Name @($Config.DeviceName | Select-Object) -ExcludeName @($Config.ExcludeDeviceName | Select-Object) | Select-Object)
if ($API) { $API.Devices = $Devices } #Give API access to the device information
if ($API) { Update-APIDeviceStatus $API $Devices } #To be removed
if ($Devices.Count -eq 0) {
Write-Log -Level Warn "No mining devices found. "
while ((Get-Date).ToUniversalTime() -lt $StatEnd) { Start-Sleep 10 }
continue
}
#Set master timer
$Timer = (Get-Date).ToUniversalTime()
$StatStart = $StatEnd
$StatEnd = $Timer.AddSeconds($Config.Interval)
$StatSpan = New-TimeSpan $StatStart $StatEnd
$DecayExponent = [int](($Timer - $DecayStart).TotalSeconds / $DecayPeriod)
$WatchdogInterval = ($WatchdogInterval / $Strikes * ($Strikes - 1)) + $StatSpan.TotalSeconds
$WatchdogReset = ($WatchdogReset / ($Strikes * $Strikes * $Strikes) * (($Strikes * $Strikes * $Strikes) - 1)) + $StatSpan.TotalSeconds
if ($API.WatchdogTimersReset) {
$WatchdogTimers = @()
$API.WatchdogTimersReset = $false
}
#Give API access to the timer information
if ($API) {
$API.Timer = $Timer
$API.StatStart = $StatStart
$API.StatEnd = $StatEnd
$API.StatSpan = $StatSpan
$API.DecayExponent = $DecayExponent
$API.WatchdogInterval = $WatchdogInterval
$API.WatchdogReset = $WatchdogReset
}
#Load information about the pools
if ((Test-Path "Pools" -PathType Container -ErrorAction Ignore) -and (-not $NewPools_Jobs)) {
if ($PoolsRequest = @(Get-ChildItem "Pools" -File -ErrorAction Ignore | Where-Object { $Config.Pools.$($_.BaseName) } | Where-Object { -not $Config.ExcludePoolName -or -not (Compare-Object @($Config.ExcludePoolName | Select-Object) @($_.BaseName | Select-Object) -IncludeEqual -ExcludeDifferent) } | Where-Object { -not $Config.PoolName -or (Compare-Object @($Config.PoolName | Foreach-Object { ($_ -split "-" | Select-Object -First ($_.BaseName -split "-").Length) -join "-" } | Select-Object) @($(for ($i = ($_.BaseName -split "-").Length; $i -ge 1; $i--) { ($_.BaseName -split "-" | Select-Object -First $i) -join "-" }) | Select-Object) -IncludeEqual -ExcludeDifferent) } | Sort-Object BaseName)) {
Write-Log "Loading pool information ($(@($PoolsRequest.BaseName) -join '; ')) - this may take a minute or two. "
$NewPools_Jobs = @(
$PoolsRequest | ForEach-Object {
$Pool_Name = $_.BaseName
$Pool_Parameters = @{StatSpan = $StatSpan; Config = $Config; JobName = "Pool_$($_.BaseName)" <#temp fix#> }
$Config.Pools.$Pool_Name | Get-Member -MemberType NoteProperty | ForEach-Object { $Pool_Parameters.($_.Name) = $Config.Pools.$Pool_Name.($_.Name) }
Get-ChildItemContent "Pools\$($_.Name)" -Parameters $Pool_Parameters -Threaded -Priority $(if ($RunningMiners | Where-Object { $_.DeviceName -like "CPU#*" }) { "Normal" })
} | Select-Object
)
if ($API) { $API.NewPools_Jobs = $NewPools_Jobs } #Give API access to pool jobs information
}
else {
Write-Log -Level Warn "No pools available. "
while ((Get-Date).ToUniversalTime() -lt $StatEnd) { Start-Sleep 10 }
continue
}
Remove-Variable PoolsRequest
}
#To minimize web requests update the pool balances every n minute or when currency or pool settings have changed; pools usually do not update the balances in real time
if (Test-Path "Balances" -PathType Container -ErrorAction Ignore) {
if ($BalancesRequest = @(Get-ChildItem "Balances" -File -ErrorAction Ignore | Where-Object { $BackupConfig.Pools.$($_.BaseName) } | Where-Object { $BackupConfig.ShowAllPoolBalances -or -not $BackupConfig.ExcludePoolName -or -not (Compare-Object @($BackupConfig.ExcludePoolName | Select-Object) @($_.BaseName | Select-Object) -IncludeEqual -ExcludeDifferent) } | Where-Object { $BackupConfig.ShowAllPoolBalances -or -not $BackupConfig.PoolName -or (Compare-Object @($BackupConfig.PoolName | Foreach-Object { ($_ -split "-" | Select-Object -First ($_.BaseName -split "-").Length) -join "-" } | Select-Object) @($(for ($i = ($_.BaseName -split "-").Length; $i -ge 1; $i--) { ($_.BaseName -split "-" | Select-Object -First $i) -join "-" }) | Select-Object) -IncludeEqual -ExcludeDifferent) } | Where-Object { ($Balances | Where-Object Pool -EQ $_.Basename).LastUpdated -lt (Get-Date).ToUniversalTime().AddMinutes(-$Config.PoolBalancesUpdateInterval) })) {
Write-Log "Loading balances information ($($BalancesRequest.BaseName -join '; ')). "
$Balances_Jobs = @(
$BalancesRequest | ForEach-Object {
$Balances_Name = $_.BaseName
$Balances_Parameters = @{JobName = "Balance_$($Balances_Name)" }
$BackupConfig.Pools.$Balances_Name | Get-Member -MemberType NoteProperty | ForEach-Object { $Balances_Parameters.($_.Name) = $BackupConfig.Pools.$Balances_Name.($_.Name) } # Use BackupConfig to not query donation balances
Get-ChildItemContent "Balances\$($_.Name)" -Parameters $Balances_Parameters -Threaded -Priority $(if ($RunningMiners | Where-Object { $_.DeviceName -like "CPU#*" }) { "Normal" })
} | Select-Object
)
if ($API) { $API.Balances_Jobs = $Balances_Jobs } #Give API access to balances jobs information
Remove-Variable BalancesRequest
}
}
#Power cost preparations
$PowerPrice = [Double]0
$PowerCostBTCperW = [Double]0
$BasePowerCost = [Double]0
if ($Devices.Count -and $Config.MeasurePowerUsage) {
#HWiNFO64 verification
$RegKey = "HKCU:\Software\HWiNFO64\VSB"
$OldRegistryValue = $RegistryValue
if ($RegistryValue = Get-ItemProperty -Path $RegKey -ErrorAction SilentlyContinue) {
if ([String]$OldRegistryValue -eq [String]$RegistryValue) {
Write-Log -Level Warn "Power usage info in registry has not been updated [HWiNFO64 not running???] - power cost calculation is not available. "
$Config.MeasurePowerUsage = $false
}
else {
$Hashtable = @{ }
$Device = ""
$RegistryValue.PsObject.Properties | Where-Object { $_.Name -match "^Label[0-9]+$" -and (Compare-Object @($_.Value -split ' ' | Select-Object) @($Devices.Name | Select-Object) -IncludeEqual -ExcludeDifferent) } | ForEach-Object {
$Device = ($_.Value -split ' ') | Select-Object -last 1
try {
$Hashtable.Add($Device, $RegistryValue.($_.Name -replace "Label", "Value"))
}
catch {
Write-Log -Level Warn "HWiNFO64 sensor naming is invalid [duplicate sensor for $Device] - disabling power usage calculations. "
$Config.MeasurePowerUsage = $false
}
}
if ($Devices.Name | Where-Object { $Hashtable.$_ -eq $null }) {
Write-Log -Level Warn "HWiNFO64 sensor naming is invalid [missing sensor config for $(($Devices.Name | Where-Object { $Hashtable.$_ -eq $null }) -join ', ')] - disabling power usage calculations. "
$Config.MeasurePowerUsage = $false
}
Remove-Variable Device
Remove-Variable HashTable
}
}
else {
Write-Log -Level Warn "Cannot read power usage info from registry [Key '$($RegKey)' does not exist - HWiNFO64 not running???] - power cost calculation is not available. "
$Config.MeasurePowerUsage = $false
}
Remove-Variable RegistryValue
Remove-Variable RegKey
}
#Retrieve collected pool data
if ($NewPools_Jobs) {
if ($NewPools_Jobs | Where-Object State -NE "Completed") { Write-Log "Waiting for pool information. " }
$NewPools = @($NewPools_Jobs | Receive-Job -Wait -AutoRemoveJob -ErrorAction SilentlyContinue | Where-Object { $_.Content.Algorithm <#temp fix, Detect broken pool files#>} | ForEach-Object { $_.Content | Add-Member Name $_.Name -Force -PassThru })
$NewPools_JobsDurations = @($NewPools_JobsDurations | Select-Object -Last 20) #Use the last 20 values for better stability
$NewPools_JobsDurations += (($NewPools_Jobs | Measure-Object PSEndTime -Maximum).Maximum - ($NewPools_Jobs | Measure-Object PSBeginTime -Minimum).Minimum).TotalSeconds
Remove-Variable NewPools_Jobs
if ($API) { $API.NewPools_Jobs = $null }
}
#TempFix: Gin and Veil are separate implementations of the same algorithm which are not compatible with all miners
$NewPools | Where-Object Algorithm -EQ "X16rt" | Where-Object CoinName -match "GinCoin|Veil" | ForEach-Object {
$Pool = $_ | ConvertTo-Json | ConvertFrom-Json
Switch ($_.CoinName) {
"GinCoin" { $Pool.Algorithm = "X16RtGin"; if (-not ($NewPools | Where-Object { $_.Name -EQ $Pool.Name -and $_.Algorithm -EQ $Pool.Algorithm })) { $NewPools += $Pool } }
"Veil" { $Pool.Algorithm = "X16RtVeil"; if (-not ($NewPools | Where-Object { $_.Name -EQ $Pool.Name -and $_.Algorithm -EQ $Pool.Algorithm })) { $NewPools += $Pool } }
}
Remove-Variable Pool
}
#Apply PricePenaltyFactor to pools
$NewPools | ForEach-Object {
if ($Config.Pools.$($_.Name).PricePenaltyFactor -gt 0) {
$_.Price = [Double]($_.Price * $Config.Pools.$($_.Name).PricePenaltyFactor)
$_.StablePrice = [Double]($_.StablePrice * $Config.Pools.$($_.Name).PricePenaltyFactor)
}
elseif ($Config.Pools.$($_.Name -split '-' | Select-Object -Index 0).PricePenaltyFactor -gt 0) {
$_.Price = [Double]($_.Price * $Config.Pools.$($_.Name -split '-' | Select-Object -Index 0).PricePenaltyFactor)
$_.StablePrice = [Double]($_.StablePrice * $Config.Pools.$($_.Name -split '-' | Select-Object -Index 0).PricePenaltyFactor)
}
}
# Calculate corrected estimates
$NewPools | ForEach-Object {
if ($_.EstimateCorrection -le 0 -or $_.EstimateCorrection -gt 1) { $_ | Add-Member EstimateCorrection ([Double]1) -Force }
if ((-not $Config.Pools.$Name.DisableEstimateCorrection) -and (-not $Config.Pools.($_.Name -split '-' | Select-Object -Index 0).DisableEstimateCorrection)) {
$_.Price *= $_.EstimateCorrection
$_.StablePrice *= $_.EstimateCorrection
}
}
if ($API) { $API.NewPools = $NewPools } #Give API access to the current running configuration
#This finds any pools that were already in $AllPools (from a previous loop) but not in $NewPools. Add them back to the list. Their API likely didn't return in time, but we don't want to cut them off just yet
#since mining is probably still working. Then it filters out any algorithms that aren't being used.
$AllPools = @(@($NewPools) + @(Compare-Object @($NewPools | Select-Object -ExpandProperty Name -Unique) @($AllPools | Select-Object -ExpandProperty Name -Unique) | Where-Object SideIndicator -EQ "=>" | Select-Object -ExpandProperty InputObject | ForEach-Object { $AllPools | Where-Object Name -EQ $_ }) |
Where-Object { $_.MarginOfError -le (1 - $Config.MinAccuracy) } |
Where-Object { $_.Price -gt 0 -and $_.StablePrice -gt 0 } |
Where-Object { -not $Config.PoolName -or (Compare-Object @($Config.PoolName | Select-Object) @($(for ($i = ($_.Name -split "-").Length; $i -ge 1; $i--) { ($_.Name -split "-" | Select-Object -First $i) -join "-" }) | Select-Object) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.ExcludePoolName -or -not (Compare-Object @($Config.ExcludePoolName | Select-Object) @($(for ($i = ($_.Name -split "-").Length; $i -ge 1; $i--) { ($_.Name -split "-" | Select-Object -First $i) -join "-" }) | Select-Object) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.Algorithm -or (Compare-Object @($Config.Algorithm | Select-Object) @($(for ($i = ($_.Algorithm -split "-").Length; $i -ge 1; $i--) { ($_.Algorithm -split "-" | Select-Object -First $i) -join "-" }) | Select-Object) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.ExcludeAlgorithm -or -not (Compare-Object @($Config.ExcludeAlgorithm | Select-Object) @($(for ($i = ($_.Algorithm -split "-").Length; $i -ge 1; $i--) { ($_.Algorithm -split "-" | Select-Object -First $i) -join "-" }) | Select-Object) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.Pools.$($_.Name).ExcludeAlgorithm -or (Compare-Object @($Config.Pools.$($_.Name).ExcludeAlgorithm | Select-Object) @($_.Algorithm, ($_.Algorithm -split "-" | Select-Object -Index 0) | Select-Object -Unique) -IncludeEqual -ExcludeDifferent | Measure-Object).Count -eq 0 } |
Where-Object { -not $Config.Pools.$($_.Name).Region -or (Compare-Object @($Config.Pools.$($_.Name).Region | Select-Object) @($_.Region) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.Pools.$($_.Name).ExcludeRegion -or -not (Compare-Object @($Config.Pools.$($_.Name).ExcludeRegion | Select-Object) @($_.Region) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.CoinName -or (Compare-Object @($Config.CoinName | Select-Object) @($_.CoinName | Select-Object) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.Pools.$($_.Name).CoinName -or (Compare-Object @($Config.Pools.$($_.Name).CoinName | Select-Object) @($_.CoinName | Select-Object) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.ExcludeCoinName -or -not (Compare-Object @($Config.ExcludeCoinName | Select-Object) @($_.CoinName | Select-Object) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.Pools.$($_.Name).ExcludeCoinName -or -not (Compare-Object @($Config.Pools.$($_.Name).ExcludeCoinName | Select-Object) @($_.CoinName | Select-Object) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.CurrencySymbol -or (Compare-Object @($Config.CurrencySymbol | Select-Object) @($_.CurrencySymbol | Select-Object) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.Pools.$($_.Name).CurrencySymbol -or (Compare-Object @($Config.Pools.$($_.Name).CurrencySymbol | Select-Object) @($_.CurrencySymbol | Select-Object) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.ExcludeCurrencySymbol -or -not (Compare-Object @($Config.ExcludeCurrencySymbol | Select-Object) @($_.CurrencySymbol | Select-Object) -IncludeEqual -ExcludeDifferent) } |
Where-Object { -not $Config.Pools.$($_.Name).ExcludeCurrencySymbol -or -not (Compare-Object @($Config.Pools.$($_.Name).ExcludeCurrencySymbol | Select-Object) @($_.CurrencySymbol | Select-Object) -IncludeEqual -ExcludeDifferent) } |
Where-Object { $Algorithm = $_.Algorithm -replace "NiceHash"<#temp fix#>; $_.Workers -eq $null -or $_.Workers -ge (($Config.MinWorker.PSObject.Properties.Name | Where-Object { $Algorithm -like $_ } | ForEach-Object { $Config.MinWorker.$_ }) | Measure-Object -Minimum).Minimum } |
Where-Object { $PoolName = $_.Name; $_.Workers -eq $null -or $_.Workers -ge (($Config.Pools.$($PoolName).MinWorker.PSObject.Properties.Name | Where-Object { $Algorithm -like $_ } | ForEach-Object { $Config.Pools.$($PoolName).MinWorker.$_ }) | Measure-Object -Minimum).Minimum } |
Sort-Object Algorithm
)
Remove-Variable NewPools
if ($API) { $API.AllPools = $AllPools } #Give API access to the current running configuration
if ($AllPools.Count -eq 0) {
Write-Log -Level Warn "No pools available. "
while ((Get-Date).ToUniversalTime() -lt $StatEnd) { Start-Sleep 10 }
continue
}
#Apply watchdog to pools
$AllPools = @(
$AllPools | Where-Object {
$Pool = $_
$Pool_WatchdogTimers = @($WatchdogTimers | Where-Object PoolName -EQ $Pool.Name | Where-Object Kicked -LT $Timer.AddSeconds( - $WatchdogInterval) | Where-Object Kicked -GT $Timer.AddSeconds( - $WatchdogReset))
($Pool_WatchdogTimers | Measure-Object | Select-Object -ExpandProperty Count) -lt <#stage#>3 -and ($Pool_WatchdogTimers | Where-Object { $Pool.Algorithm -contains $_.Algorithm } | Measure-Object | Select-Object -ExpandProperty Count) -lt <#statge#>2
}
)
#Update the active pools
Write-Log "Selecting best pool for each algorithm (from $($AllPools.Count) pools). "
$Pools = [PSCustomObject]@{ }
$AllPools.Algorithm | Select-Object | ForEach-Object { $_.ToLower() } | Select-Object -Unique | ForEach-Object { $Pools | Add-Member $_ ($AllPools | Where-Object Algorithm -EQ $_ | Sort-Object -Descending { -not $Config.PoolName -or (Compare-Object @($Config.PoolName | Select-Object) @($(for ($i = ($_.Name -split "-").Length; $i -ge 1; $i--) { ($_.Name -split "-" | Select-Object -First $i) -join "-" }) | Select-Object) -IncludeEqual -ExcludeDifferent) }, { ($Timer - $_.Updated).TotalMinutes -le ($SyncWindow * $Strikes) }, { $_.StablePrice * (1 - $_.MarginOfError) }, { $_.Region -EQ $Config.Region }, { $_.SSL -EQ $Config.SSL } | Select-Object -Index 0) }
if (($Pools | Get-Member -MemberType NoteProperty -ErrorAction Ignore | Select-Object -ExpandProperty Name | ForEach-Object { $Pools.$_.Name } | Select-Object -Unique | ForEach-Object { $AllPools | Where-Object Name -EQ $_ | Measure-Object Updated -Maximum | Select-Object -ExpandProperty Maximum } | Measure-Object -Minimum -Maximum | ForEach-Object { $_.Maximum - $_.Minimum } | Select-Object -ExpandProperty TotalMinutes) -gt $SyncWindow) {
Write-Log -Level Warn "Pool prices are out of sync ($([Int]($Pools | Get-Member -MemberType NoteProperty -ErrorAction Ignore | Select-Object -ExpandProperty Name | ForEach-Object {$Pools.$_} | Measure-Object Updated -Minimum -Maximum | ForEach-Object {$_.Maximum - $_.Minimum} | Select-Object -ExpandProperty TotalMinutes)) minutes). "
$Pools | Get-Member -MemberType NoteProperty -ErrorAction Ignore | Select-Object -ExpandProperty Name | ForEach-Object { $Pools.$_ | Add-Member Price_Bias ($Pools.$_.StablePrice * (1 - ($Pools.$_.MarginOfError * $(if ($Pools.$_.PayoutScheme -eq "PPLNS") { $Config.SwitchingPrevention } else { 1 }) * (1 - $Pools.$_.Fee) * [Math]::Pow($DecayBase, $DecayExponent)))) -Force }
$Pools | Get-Member -MemberType NoteProperty -ErrorAction Ignore | Select-Object -ExpandProperty Name | ForEach-Object { $Pools.$_ | Add-Member Price_Unbias ($Pools.$_.StablePrice * (1 - $Pools.$_.Fee)) -Force }
}
else {
$Pools | Get-Member -MemberType NoteProperty -ErrorAction Ignore | Select-Object -ExpandProperty Name | ForEach-Object { $Pools.$_ | Add-Member Price_Bias ($Pools.$_.Price * (1 - ($Pools.$_.MarginOfError * $(if ($Pools.$_.PayoutScheme -eq "PPLNS") { $Config.SwitchingPrevention } else { 1 }) * (1 - $Pools.$_.Fee) * [Math]::Pow($DecayBase, $DecayExponent)))) -Force }
$Pools | Get-Member -MemberType NoteProperty -ErrorAction Ignore | Select-Object -ExpandProperty Name | ForEach-Object { $Pools.$_ | Add-Member Price_Unbias ($Pools.$_.Price * (1 - $Pools.$_.Fee)) -Force }
}
if ($API) { $API.Pools = $Pools } #Give API access to the pools information
#Load the stats, to improve performance only read PowerUsage stats when required
Write-Log "Loading saved statistics. "
Get-Stat | Out-Null #temp fix for legacy miners
if ($API) { $API.Stats = $Stats } #Give API access to the current stats
#Load legacy miners
#To-do: change dot to ampersand
if (Test-Path .\Convert-LegacyMiners.ps1) { . .\Convert-LegacyMiners.ps1 }
#Check for failed miner
$RunningMiners | Where-Object { $_.GetStatus() -ne "Running" } | ForEach-Object {
$_.StatusMessage = " exited unexpectedly"
$_.SetStatus("Failed")
Write-Log -Level Error "Miner ($($_.Name) {$(($_.Algorithm | ForEach-Object { "$($_)@$($Pools.$_.Name)" }) -join "; ")})$(if ($_.StatusMessage) { $_.StatusMessage } else { " has failed" }). "
#Post miner failure exec
$Command = ($ExecutionContext.InvokeCommand.ExpandString((Get-PrePostCommand -Miner $_ -Config $Config -Event "PostStop"))).Trim()
if ($Command) { Start-PrePostCommand -Command $Command -Event "PostStop" }
Remove-Variable Command
}
#Don't penalize active or benchmarking miners
$ActiveMiners | Where-Object { $_.GetStatus() -EQ "Running" -or ($_.Intervals.Count -and $_.Speed -contains $null) } | ForEach-Object { $_.Earning_Bias = $_.Earning_Unbias; $_.Profit_Bias = $_.Profit_Unbias }
#Update API miner information
if ($API) {
$API.ActiveMiners = $ActiveMiners
$API.RunningMiners = @($ActiveMiners | Where-Object { $_.GetStatus() -eq "Running" })
$API.FailedMiners = @($ActiveMiners | Where-Object { $_.GetStatus() -eq "Failed" })
Update-APIDeviceStatus $API $Devices
}
#Hack: temporarily make all earnings & profits positive, BestMiners_Combos(_Comparison) produces wrong sort order when earnings or profits are negative
$SmallestEarningBias = ([Double][Math]::Abs(($ActiveMiners | Sort-Object Earning_Bias | Select-Object -Index 0).Earning_Bias)) * 2
$SmallestEarningComparison = ([Double][Math]::Abs(($ActiveMiners | Sort-Object Earning_Comparison | Select-Object -Index 0).Earning_Comparison)) * 2
$SmallestProfitBias = ([Double][Math]::Abs(($ActiveMiners | Sort-Object Profit_Bias | Select-Object -Index 0).Profit_Bias)) * 2
$SmallestProfitComparison = ([Double][Math]::Abs(($ActiveMiners | Sort-Object Profit_Comparison | Select-Object -Index 0).Profit_Comparison)) * 2
$ActiveMiners | Where-Object { $null -ne $_.Earning_Bias } | ForEach-Object { $_.Earning_Bias += $SmallestEarningBias; $_.Earning_Comparison += $SmallestEarningComparison; $_.Profit_Bias += $SmallestProfitBias; $_.Profit_Comparison += $SmallestProfitComparison }
#Get most profitable miner combination i.e. AMD+NVIDIA+CPU
if ($Config.IgnorePowerCost) {
$BestMiners = @($ActiveMiners | Select-Object DeviceName -Unique | ForEach-Object { $Miner_GPU = $_; ($ActiveMiners | Where-Object { (Compare-Object $Miner_GPU.DeviceName $_.DeviceName | Measure-Object).Count -eq 0 -and $_.Earning -ne 0 } | Sort-Object -Descending { ($_ | Where-Object Speed -contains $null | Measure-Object).Count }, { $(if ($_.Speed -contains $null) { $_.Intervals.Count, $_.IntervalMultiplier } else { 0, 0 }) }, { $Config.MeasurePowerUsage -and $_.PowerUsage -le 0 }, { $_.Earning_Bias } | Select-Object -Index 0) })
$BestMiners_Comparison = @($ActiveMiners | Select-Object DeviceName -Unique | ForEach-Object { $Miner_GPU = $_; ($ActiveMiners | Where-Object { (Compare-Object $Miner_GPU.DeviceName $_.DeviceName | Measure-Object).Count -eq 0 -and $_.Earning -ne 0 } | Sort-Object -Descending { ($_ | Where-Object Speed -contains $null | Measure-Object).Count }, { $(if ($_.Speed -contains $null) { $_.Intervals.Count, $_.IntervalMultiplier } else { 0, 0 }) }, { $Config.MeasurePowerUsage -and $_.PowerUsage -le 0 }, { $_.Earning_Comparison } | Select-Object -Index 0) })
}
else {
$BestMiners = @($ActiveMiners | Select-Object DeviceName -Unique | ForEach-Object { $Miner_GPU = $_; ($ActiveMiners | Where-Object { (Compare-Object $Miner_GPU.DeviceName $_.DeviceName | Measure-Object).Count -eq 0 -and $_.Earning -ne 0 } | Sort-Object -Descending { ($_ | Where-Object Speed -contains $null | Measure-Object).Count }, { $(if ($_.Speed -contains $null) { $_.Intervals.Count, $_.IntervalMultiplier } else { 0, 0 }) }, { $Config.MeasurePowerUsage -and $_.PowerUsage -le 0 }, { $_.Profit_Bias } | Select-Object -Index 0) })
$BestMiners_Comparison = @($ActiveMiners | Select-Object DeviceName -Unique | ForEach-Object { $Miner_GPU = $_; ($ActiveMiners | Where-Object { (Compare-Object $Miner_GPU.DeviceName $_.DeviceName | Measure-Object).Count -eq 0 -and $_.Earning -ne 0 } | Sort-Object -Descending { ($_ | Where-Object Speed -contains $null | Measure-Object).Count }, { $(if ($_.Speed -contains $null) { $_.Intervals.Count, $_.IntervalMultiplier } else { 0, 0 }) }, { $Config.MeasurePowerUsage -and $_.PowerUsage -le 0 }, { $_.Profit_Comparison } | Select-Object -Index 0) })
}
$Miners_Device_Combos = @(Get-Combination ($ActiveMiners | Select-Object DeviceName -Unique) | Where-Object { (Compare-Object ($_.Combination | Select-Object -ExpandProperty DeviceName -Unique) ($_.Combination | Select-Object -ExpandProperty DeviceName) | Measure-Object).Count -eq 0 })
$BestMiners_Combos = @(
$Miners_Device_Combos | ForEach-Object {
$Miner_Device_Combo = $_.Combination
[PSCustomObject]@{
Combination = $Miner_Device_Combo | ForEach-Object {
$Miner_Device_Count = $_.DeviceName.Count
[Regex]$Miner_Device_Regex = "^(" + (($_.DeviceName | ForEach-Object { [Regex]::Escape($_) }) -join '|') + ")$"
$BestMiners | Where-Object { ([Array]$_.DeviceName -notmatch $Miner_Device_Regex).Count -eq 0 -and ([Array]$_.DeviceName -match $Miner_Device_Regex).Count -eq $Miner_Device_Count }
}
}
}
)
$BestMiners_Combos_Comparison = @(
$Miners_Device_Combos | ForEach-Object {
$Miner_Device_Combo = $_.Combination
[PSCustomObject]@{
Combination = $Miner_Device_Combo | ForEach-Object {
$Miner_Device_Count = $_.DeviceName.Count
[Regex]$Miner_Device_Regex = "^(" + (($_.DeviceName | ForEach-Object { [Regex]::Escape($_) }) -join '|') + ")$"
$BestMiners_Comparison | Where-Object { ([Array]$_.DeviceName -notmatch $Miner_Device_Regex).Count -eq 0 -and ([Array]$_.DeviceName -match $Miner_Device_Regex).Count -eq $Miner_Device_Count }
}
}
}
)
$BestMiners_Combo = @($BestMiners_Combos | Sort-Object -Descending { ($_.Combination | Where-Object Earning -EQ $null | Measure-Object).Count }, { ($_.Combination | Measure-Object Earning_Bias -Sum).Sum }, { ($_.Combination | Where-Object Earning -NE 0 | Measure-Object).Count } | Select-Object -Index 0 | Select-Object -ExpandProperty Combination)
$BestMiners_Combo_Comparison = @($BestMiners_Combos_Comparison | Sort-Object -Descending { ($_.Combination | Where-Object Earning -EQ $null | Measure-Object).Count }, { ($_.Combination | Measure-Object Earning_Comparison -Sum).Sum }, { ($_.Combination | Where-Object Earning -NE 0 | Measure-Object).Count } | Select-Object -Index 0 | Select-Object -ExpandProperty Combination)
if ($ActiveMiners.Count -eq 1) {
$BestMiners_Combo_Comparison = $BestMiners_Combo = @($ActiveMiners)
}
#ProfitabilityThreshold check
$MiningEarning = (($BestMiners_Combo | Measure-Object Earning -Sum).Sum) * $Rates.BTC.$FirstCurrency
$MiningProfit = (($BestMiners_Combo | Measure-Object Profit -Sum).Sum) * $Rates.BTC.$FirstCurrency
$MiningCost = (($BestMiners_Combo | Measure-Object PowerCost -Sum).Sum + $BasePowerCost) * $Rates.BTC.$FirstCurrency
if ($API) {
$API.BestMiners = $BestMiners
$API.BestMiners_Comparison = $BestMiners_Comparison
$API.BestMiners_Combos = $BestMiners_Combos
$API.BestMiners_Combos_Comparison = $BestMiners_Combos_Comparison
$API.BestMiners_Combo = $BestMiners_Combo
$API.BestMiners_Combo_Comparison = $BestMiners_Combo_Comparison
$API.MiningEarning = $MiningEarning
$API.MiningProfit = $MiningProfit
$API.MiningCost = $MiningCost
}
#OK to run miners?
if (($MiningEarning - $MiningCost) -ge $Config.ProfitabilityThreshold -or $MinersNeedingBenchmark.Count -gt 0 -or $MinersNeedingPowerUsageMeasurement.Count -gt 0) {
$BestMiners_Combo | ForEach-Object { $_.Best = $true }
$BestMiners_Combo_Comparison | ForEach-Object { $_.Best_Comparison = $true }
}
Remove-Variable Miner_Device_Combo
Remove-Variable Miners_Device_Combos
Remove-Variable BestMiners
Remove-Variable BestMiners_Comparison
#Hack part 2: reverse temporarily forced positive earnings & profits
$ActiveMiners | Where-Object { $null -ne $_.Earning_Bias } | ForEach-Object { $_.Earning_Bias -= $SmallestEarningBias; $_.Earning_Comparison -= $SmallestEarningComparison; $_.Profit_Bias -= $SmallestProfitBias; $_.Profit_Comparison -= $SmallestProfitComparison }
Remove-Variable SmallestEarningBias
Remove-Variable SmallestEarningComparison
Remove-Variable SmallestProfitBias
Remove-Variable SmallestProfitComparison
#Stop miners in the active list depending on if they are the most profitable
$ActiveMiners | Where-Object { $_.GetActivateCount() } | Where-Object { $Miner = $_; $_.Best -EQ $false -or (-not ($Miner.Benchmarked % $Miner.IntervalMultiplier) -and [Boolean]($Miner.Algorithm | Where-Object { -not (Get-Stat -Name "$($Miner.Name)_$($_)_HashRate") })) -or ($Config.ShowMinerWindow -ne $OldConfig.ShowMinerWindow) } | ForEach-Object {
$Miner = $_
$RunningMiners = $RunningMiners | Where-Object $_ -NE $Miner
if ($Miner.GetStatus() -eq "Running") {
#Pre miner start exec
$Command = ($ExecutionContext.InvokeCommand.ExpandString((Get-PrePostCommand -Miner $Miner -Config $Config -Event "PreStop"))).Trim()
if ($Command) { Start-PrePostCommand -Command $Command -Event "PreStop" }
Write-Log "Stopping miner ($($Miner.Name) {$(($Miner.Algorithm | ForEach-Object { "$($_)@$($Miner.PoolName | Select-Object -Index ([array]::indexof($Miner.Algorithm, $_)))" }) -join "; ")}). "
$Miner.SetStatus("Idle")
$Miner.StatusMessage = " stopped gracefully"
if ($Miner.ProcessId -and -not ($ActiveMiners | Where-Object { $_.Best -and $_.API -EQ $Miner.API })) { Stop-Process -Id $Miner.ProcessId -Force -ErrorAction Ignore } #temp fix
#Post miner stop exec
$Command = ($ExecutionContext.InvokeCommand.ExpandString((Get-PrePostCommand -Miner $Miner -Config $Config -Event "PostStop"))).Trim()
if ($Command) { Start-PrePostCommand -Command $Command -Event "PostStop" }
#Remove watchdog timer
$Miner_IntervalMultiplier = $Miner.IntervalMultiplier
$Miner.Algorithm | ForEach-Object {
$Miner_Algorithm = $_
$WatchdogTimer = $WatchdogTimers | Where-Object { $_.MinerName -eq $Miner.Name -and $_.PoolName -eq $Pools.$Miner_Algorithm.Name -and $_.Algorithm -eq $Miner_Algorithm }
if ($WatchdogTimer) {
if ($WatchdogTimer.Kicked -lt $Timer.AddSeconds( - $WatchdogInterval * $Miner_IntervalMultiplier)) {
$Miner.SetStatus("Failed")
$Miner.StatusMessage = " was temporarily disabled by watchdog"
Write-Log -Level Warn "Watchdog: Miner ($Miner.Name {$(($Miner.Algorithm | ForEach-Object { "$($_)@$($Pools.$_.Name)" }) -join "; ")}) temporarily disabled. "
}
else {
$WatchdogTimers = @($WatchdogTimers -notmatch $WatchdogTimer)
}
}
}
}
}
if ($API) { $API.WatchdogTimers = $WatchdogTimers } #Give API access to WatchdogTimers information
Start-Sleep $Config.Delay #Wait to prevent BSOD
#Kill stray miners
Get-CIMInstance CIM_Process | Where-Object ExecutablePath | Where-Object { $AllMinerPaths -contains $_.ExecutablePath } | Where-Object { $ActiveMiners.ProcessID -notcontains $_.ProcessID } | Select-Object -ExpandProperty ProcessID | ForEach-Object { Stop-Process -Id $_ -Force -ErrorAction Ignore }
$FailedMiners = @($null)
if ($API.FailedMiners) { $API.FailedMiners = $null }
$RunningMiners = @($ActiveMiners | Where-Object Best | Where-Object { $_.GetStatus() -eq "Running" })
if ($ActiveMiners.Count -eq 0) {
Write-Log -Level Warn "No active miners available. "
if ($Downloader) { $Downloader | Receive-Job -ErrorAction SilentlyContinue }
Start-Sleep 10
continue
}
#Start miners in the active list depending on if they are the most profitable
$ActiveMiners | Where-Object Best | ForEach-Object {
$Miner = $_
if ($_.GetStatus() -ne "Running") {
#Pre miner start exec
$Command = ($ExecutionContext.InvokeCommand.ExpandString((Get-PrePostCommand -Miner $Miner -Config $Config -Event "PreStart"))).Trim()
if ($Command) { Start-PrePostCommand -Command $Command -Event "PreStart" }
Remove-Variable Command
Write-Log "Starting miner ($($Miner.Name) {$(($Miner.Algorithm | ForEach-Object { "$($_)@$($Pools.$_.Name)" }) -join "; ")}). "
Write-Log -Level Verbose $Miner.GetCommandLine().Replace("$(Convert-Path '.\')\", "")
$Miner.SetStatus("Running")
$RunningMiners += $Miner #Update API miner information
if ($API) { $API.RunningMiners = $RunningMiners }
#Post miner start exec
$Command = ($ExecutionContext.InvokeCommand.ExpandString((Get-PrePostCommand -Miner $Miner -Config $Config -Event "PostStart"))).Trim()
if ($Command) { Start-PrePostCommand -Command $Command -Event "PostStart" }
Remove-Variable Command
#Add watchdog timer
if ($Config.Watchdog -and ($Miner.Algorithm | Where-Object { (Get-Stat -Name "$($Miner.Name)_$($_)_HashRate") }) -and -not $(if ($Config.MeasurePowerUsage) { $Miner.PowerUsage -EQ $null })) {
$Miner.Algorithm | ForEach-Object {
$Miner_Algorithm = $_
$WatchdogTimer = $WatchdogTimers | Where-Object { $_.MinerName -eq $Miner.Name -and $_.PoolName -eq $Pools.$Miner_Algorithm.Name -and $_.Algorithm -eq $Miner_Algorithm }
if (-not $WatchdogTimer) {
$WatchdogTimers += [PSCustomObject]@{
MinerName = $Miner.Name
PoolName = $Pools.$Miner_Algorithm.Name
Algorithm = $Miner_Algorithm
Device = "{$($Miner.DeviceName -join "; ")}"
Kicked = $Timer
}
}
elseif (-not ($WatchdogTimer.Kicked -GT $Timer.AddSeconds( - $WatchdogReset))) {
$WatchdogTimer.Kicked = $Timer
}
}
}
}
if ($Miner.Speed -contains $null) {
Write-Log -Level Warn "Benchmarking miner ($($Miner.Name) {$(($Miner.Algorithm | ForEach-Object { "$($_)@$($Miner.PoolName | Select-Object -Index ([array]::indexof($Miner.Algorithm, $_)))" }) -join "; ")})$(if ($Miner.IntervalMultiplier -gt 1) {" requires extended benchmark duration (Benchmarking interval $($_.Intervals.Count + 1)/$($_.IntervalMultiplier))" }) [Attempt $($_.GetActivateCount()) of max. $Strikes]. "
}
else {
if ($Config.MeasurePowerUsage -and $Miner.Algorithm | Where-Object { -not (Get-Stat -Name "$($Miner.Name)$(if (@($Miner.Algorithm).Count -eq 1) { "_$($Miner.Algorithm)" })_PowerUsage") }) {
Write-Log -Level Warn "Measuring power usage for miner ($($Miner.Name) {$(($Miner.Algorithm | ForEach-Object { "$($_)@$($Miner.PoolName | Select-Object -Index ([array]::indexof($Miner.Algorithm, $_)))" }) -join "; ")})$(if ($Miner.IntervalMultiplier -gt 1) { " requires extended power measurement duration (Measurement interval $($_.Intervals.Count + 1)/$($_.IntervalMultiplier))" }) [Attempt $($_.GetActivateCount()) of max. $Strikes]. "
}
}
if ($API) { $API.WatchdogTimers = $WatchdogTimers } #Give API access to WatchdogTimers information
}
Clear-Host
#Display mining information
[System.Collections.ArrayList]$Miner_Table = @(
@{Width = [Int]($Miners.Name | Measure-Object Length -Maximum).maximum; Label = "Miner[Fee]"; Expression = { "$($_.Name)$(($_.Fees.PSObject.Properties.Value | ForEach-Object {"[{0:P2}]" -f [Double]$_}) -join '')" } },
@{Width = [Int]($Miners | ForEach-Object { $_.HashRates.PSObject.Properties.Name -join " " } | Measure-Object Length -Maximum).maximum; Label = "Algorithm"; Expression = { $Miner = $_; $_.HashRates.PSObject.Properties.Name } },
@{Width = [Int]($(if ($MinersNeedingBenchmark.Count) { 21 }), (($Miners | ForEach-Object { ($_.HashRates.PSObject.Properties.Value | ConvertTo-Hash) -join " " } | Measure-Object Length -Maximum).maximum + 2) | Measure-Object -Maximum).Maximum; Label = "Speed"; Expression = { $Miner = $_; $_.HashRates.PSObject.Properties.Value | ForEach-Object { if ($_ -ne $null) { "$($_ | ConvertTo-Hash)/s" } else { $(if ($RunningMiners | Where-Object { $_.Path -eq $Miner.Path -and $_.Arguments -EQ $Miner.Arguments }) { "Benchmark in progress" } else { "Benchmark pending" }) } } }; Align = 'right' }
)
if ($PowerPrice) {
$Miner_Table.AddRange(
@(
#Mining Profits
@{Width = [Int](7, ((ConvertTo-LocalCurrency -Value ($Miners.Profit | Sort-Object | Select-Object -Index 0) -BTCRate ($Rates.BTC.$FirstCurrency)).Length) | Measure-Object -Maximum).Maximum; Label = "Profit`n$($FirstCurrency)/Day"; Expression = { if ($_.Profit) { ConvertTo-LocalCurrency -Value ($_.Profit) -BTCRate ($Rates.BTC.$FirstCurrency) -Offset 1 } else { "Unknown" } }; Align = "right" },
@{Width = [Int](7, ((ConvertTo-LocalCurrency -Value ($Miners.Profit_Bias | Sort-Object | Select-Object -Index 0) -BTCRate ($Rates.BTC.$FirstCurrency)).Length) | Measure-Object -Maximum).Maximum; Label = "Profit Bias`n$($FirstCurrency)/Day"; Expression = { if ($_.Profit_Bias) { ConvertTo-LocalCurrency -Value ($_.Profit_Bias) -BTCRate ($Rates.BTC.$FirstCurrency) -Offset 1 } else { "Unknown" } }; Align = "right" }
)
)
}
$Miner_Table.AddRange(
@(
#Miner earnings
@{Width = [Int](7, ((ConvertTo-LocalCurrency -Value ($Miners.Earning | Sort-Object | Select-Object -Index 0) -BTCRate ($Rates.BTC.$FirstCurrency)).Length) | Measure-Object -Maximum).Maximum; Label = "Earning`n$($FirstCurrency)/Day"; Expression = { if ($_.Earning) { ConvertTo-LocalCurrency -Value ($_.Earning) -BTCRate ($Rates.BTC.$FirstCurrency) -Offset 1 } else { "Unknown" } }; Align = "right" },
@{Width = [Int](7, ((ConvertTo-LocalCurrency -Value ($Miners.Earning_Bias | Sort-Object | Select-Object -Index 0) -BTCRate ($Rates.BTC.$FirstCurrency)).Length) | Measure-Object -Maximum).Maximum; Label = "Earning Bias`n$($FirstCurrency)/Day"; Expression = { if ($_.Earning_Bias) { ConvertTo-LocalCurrency -Value ($_.Earning_Bias) -BTCRate ($Rates.BTC.$FirstCurrency) -Offset 1 } else { "Unknown" } }; Align = "right" }
)
)
if ($PowerPrice) {
$Miner_Table.AddRange(
@(
#PowerCost
@{Width = [Int](7, ((ConvertTo-LocalCurrency -Value ($Miners.PowerCost | Sort-Object | Select-Object -Index 0) -BTCRate ($Rates.BTC.$FirstCurrency)).Length) | Measure-Object -Maximum).Maximum; Label = "Power Cost`n$($FirstCurrency)/Day"; Expression = { if ($PowerPrice -eq 0) { "$(ConvertTo-LocalCurrency -Value 0 -BTCRate ($Rates.BTC.$FirstCurrency) -Offset 1)" } else { if ($_.PowerUsage) { "-$(ConvertTo-LocalCurrency -Value ($_.PowerCost) -BTCRate ($Rates.BTC.$FirstCurrency) -Offset 1)" } else { "Unknown" } } }; Align = "right" }
)
)
}
if ($Config.MeasurePowerUsage -and $Config.ShowPowerUsage) {
$Miner_Table.AddRange(
@(
#Power Usage
@{Width = 12; Label = "Power Usage`nWatt"; Expression = { $Miner = $_; if ($_.PowerUsage) { "$($_.PowerUsage.ToString("N2"))" } else { if ($RunningMiners | Where-Object { $_.Path -eq $Miner.Path -and $_.Arguments -EQ $Miner.Arguments }) { "Measuring..." } else { "Unmeasured" } } }; Align = "right" }
)
)
}
$Miner_Table.AddRange(
@(
@{Width = 12; Label = "Accuracy"; Expression = { $_.Pools.PSObject.Properties.Value | ForEach-Object { "{0:P0}" -f [Double](1 - $_.MarginOfError) } }; Align = 'right' },
@{Width = 15; Label = "$($FirstCurrency)/GH/Day"; Expression = { $_.Pools.PSObject.Properties.Value | ForEach-Object { ConvertTo-LocalCurrency -Value ($_.Price * 1000000000) -BTCRate ($Rates.BTC.$FirstCurrency) -Offset 4 } }; Align = "right" },
@{Width = [Int](($Miners | ForEach-Object Name | Measure-Object Length -Maximum).maximum + ($Miners | ForEach-Object CoinName | Measure-Object Length -Maximum).maximum); Label = "Pool[Fee]"; Expression = { $_.Pools.PSObject.Properties.Value | ForEach-Object { "$(($_.Name, $_.CoinName | Where-Object { $_ } ) -join '-')$("[{0:P2}]" -f [Double]$_.Fee)" } } }
)
)
$Miners | Group-Object -Property { $_.DeviceName } | ForEach-Object {
$MinersDeviceGroup = @($_.Group)
$MinersDeviceGroupNeedingBenchmark = @($MinersDeviceGroup | Where-Object { $_.HashRates.PSObject.Properties.Value -contains $null })
$MinersDeviceGroupNeedingPowerUsageMeasurement = @($(if ($Config.MeasurePowerUsage) { @($MinersDeviceGroup | Where-Object PowerUsage -le 0) }))
$MinersDeviceGroup | Where-Object {
$Config.ShowAllMiners -or <#List all miners#>
$MinersDeviceGroupNeedingBenchmark.Count -or <#List all miners when benchmarking#>
$MinersDeviceGroupNeedingPowerUsageMeasurement.Count -or <#List all miners when measuring power consumption#>
$_.Earning_Unbias -ge ($MinersDeviceGroup.Earning_Unbias | Sort-Object -Descending | Select-Object -Index 4) -or <#Always list at least the top 5 unbiased earning miners per device group#>
$_.Earning -ge ($MinersDeviceGroup.Earning | Sort-Object -Descending | Select-Object -Index 4) -or <#Always list at least the top 5 earning miners per device group#>
$_.Earning_Unbias -ge (($MinersDeviceGroup.Earning_Unbias | Sort-Object -Descending | Select-Object -Index 0) * 0.5) -or <#Always list the better 50% unbiased earning miners per device group#>
$_.Earning -ge (($MinersDeviceGroup.Earning | Sort-Object -Descending | Select-Object -Index 0) * 0.5) <#Always list the better 50% earning miners per device group#>
} | Sort-Object DeviceName, @{ Expression = $(if ($Config.IgnorePowerCost) { "Earning_Bias" } else { "Profit_Bias" } ); Descending = $True }, @{ Expression = { $_.HashRates.PSObject.Properties.Name } } | Format-Table $Miner_Table -GroupBy @{ Name = "Device$(if (@($_).Count -ne 1) { "s" })"; Expression = { "$($_.DeviceName -join ', ') [$(($Devices | Where-Object Name -eq $_.DeviceName).Model -join ', ')]" } } | Out-Host
#Display benchmarking progress
if ($MinersDeviceGroupNeedingBenchmark) {
Write-Log -Level Warn "Benchmarking for device$(if (($MinersDeviceGroup | Select-Object -Unique).Count -ne 1) { " group" } ) ($(($MinersDeviceGroup.DeviceName | Select-Object -Unique ) -join '; ')) in progress: $($MinersDeviceGroupNeedingBenchmark.Count) miner$(if ($MinersDeviceGroupNeedingBenchmark.Count -gt 1){ 's' }) left to complete benchmark."
}
#Display power usage measurement progress
if ($MinersDeviceGroupNeedingPowerUsageMeasurement) {
Write-Log -Level Warn "Power usage measurement for device$(if (($MinersDeviceGroup | Select-Object -Unique).Count -ne 1) { " group" } ) ($(($MinersDeviceGroup.DeviceName | Select-Object -Unique ) -join '; ')) in progress: $($MinersDeviceGroupNeedingPowerUsageMeasurement.Count) miner$(if ($MinersDeviceGroupNeedingPowerUsageMeasurement.Count -gt 1) { 's' }) left to complete measuring."
}
}
Remove-Variable MinersDeviceGroup
Remove-Variable Miner_Table
#Display active miners list
$ActiveMiners | Where-Object { $_.GetActivateCount() } | Sort-Object -Property @{Expression = { $_.GetStatus() }; Descending = $False }, @{Expression = { $_.GetActiveLast() }; Descending = $True } | Select-Object -First (1 + 6 + 6) | Format-Table -Wrap -GroupBy @{ Label = "Status"; Expression = { $_.GetStatus() } } (
@{Label = "Last Speed"; Expression = { $_.Speed_Live | ForEach-Object { "$($_ | ConvertTo-Hash)/s" } }; Align = 'right' },
@{Label = "Active"; Expression = { "{0:dd} Days {0:hh} Hours {0:mm} Minutes" -f $_.GetActiveTime() } },
@{Label = "Launched"; Expression = { Switch ($_.GetActivateCount()) { 0 { "Never" } 1 { "Once" } Default { "$_ Times" } } } },
@{Label = "Miner"; Expression = { $_.Name } },
@{Label = "Command"; Expression = { $_.GetCommandLine().Replace("$(Convert-Path '.\')\", "") } }
) | Out-Host
#Display watchdog timers
$WatchdogTimers | Where-Object Kicked -GT $Timer.AddSeconds( - $WatchdogReset) | Format-Table -Wrap (
@{Label = "Miner"; Expression = { $_.MinerName } },
@{Label = "Pool"; Expression = { $_.PoolName } },
@{Label = "Algorithm"; Expression = { $_.Algorithm } },
@{Label = "Watchdog Timer"; Expression = { "{0:n0} Seconds" -f ($Timer - $_.Kicked | Select-Object -ExpandProperty TotalSeconds) }; Align = 'right' }
) | Out-Host
#Display profit comparison
if (-not ($BestMiners_Combo | Where-Object Profit -EQ $null) -and $Downloader.State -eq "Running") { $Downloader | Wait-Job -Timeout 10 | Out-Null }
if (-not ($BestMiners_Combo | Where-Object { $_.Profit -eq $null -or ($Config.MeasurePowerUsage -and $_.PowerUsage -eq $null) }) -and $Downloader.State -ne "Running") {
$MinerComparisons =
[PSCustomObject]@{"Miner" = "MultiPoolMiner" },
[PSCustomObject]@{"Miner" = $BestMiners_Combo_Comparison | ForEach-Object { "$($_.Name)-$($_.Algorithm -join '/')" } }
$BestMiners_Combo_Stat = Set-Stat -Name "Profit" -Value ($BestMiners_Combo | Measure-Object Profit -Sum).Sum -Duration $StatSpan
$MinerComparisons_Profit = $BestMiners_Combo_Stat.Week, ($BestMiners_Combo_Comparison | Measure-Object Profit_Comparison -Sum).Sum
$MinerComparisons_MarginOfError = $BestMiners_Combo_Stat.Week_Fluctuation, ($BestMiners_Combo_Comparison | ForEach-Object { $_.Profit_MarginOfError * (& { if ($MinerComparisons_Profit[1]) { $_.Profit_Comparison / $MinerComparisons_Profit[1] }else { 1 } }) } | Measure-Object -Sum).Sum
$Config.Currency | Where-Object { $Rates.BTC.$_ } | ForEach-Object {
$MinerComparisons[0] | Add-Member $_.ToUpper() ("{0:N5} $([Char]0x00B1){1:P0} ({2:N5}-{3:N5})" -f ($MinerComparisons_Profit[0] * $Rates.BTC.$_), $MinerComparisons_MarginOfError[0], (($MinerComparisons_Profit[0] * $Rates.BTC.$_) / (1 + $MinerComparisons_MarginOfError[0])), (($MinerComparisons_Profit[0] * $Rates.BTC.$_) * (1 + $MinerComparisons_MarginOfError[0])))
$MinerComparisons[1] | Add-Member $_.ToUpper() ("{0:N5} $([Char]0x00B1){1:P0} ({2:N5}-{3:N5})" -f ($MinerComparisons_Profit[1] * $Rates.BTC.$_), $MinerComparisons_MarginOfError[1], (($MinerComparisons_Profit[1] * $Rates.BTC.$_) / (1 + $MinerComparisons_MarginOfError[1])), (($MinerComparisons_Profit[1] * $Rates.BTC.$_) * (1 + $MinerComparisons_MarginOfError[1])))
}
if ($MinerComparisons_Profit[0] -gt $MinerComparisons_Profit[1]) {
$MinerComparisons_Range = ($MinerComparisons_MarginOfError | Measure-Object -Average | Select-Object -ExpandProperty Average), (($MinerComparisons_Profit[0] - $MinerComparisons_Profit[1]) / $MinerComparisons_Profit[1]) | Measure-Object -Minimum | Select-Object -ExpandProperty Minimum
Write-Host -BackgroundColor Yellow -ForegroundColor Black "MultiPoolMiner is between $([Math]::Abs([Math]::Round((((($MinerComparisons_Profit[0]-$MinerComparisons_Profit[1])/$MinerComparisons_Profit[1])-$MinerComparisons_Range)*100))))% and $([Math]::Abs([Math]::Round((((($MinerComparisons_Profit[0]-$MinerComparisons_Profit[1])/$MinerComparisons_Profit[1])+$MinerComparisons_Range)*100))))% more profitable than the fastest miner$(if ($BestMiners_Combo.Count -ne 1) { "s" }): "
Remove-Variable MinerComparisons_Range
}
$MinerComparisons | Out-Host
Remove-Variable MinerComparisons_MarginOfError
Remove-Variable MinerComparisons_Profit
Remove-Variable BestMiners_Combo_Stat
Remove-Variable MinerComparisons
}
Remove-Variable BestMiners_Combo
Remove-Variable BestMiners_Combo_Comparison
#Display pool balances
if ($Balances) {
Write-Host "Pool Balances: $(($Config.Currency | Where-Object {$Rates.$Currency.$_} | ForEach-Object { "$(($Balances | Where-Object { $Rates.($_.Currency).$Currency } | ForEach-Object { $_.Total * $Rates.($_.Currency).$Currency } | Measure-Object -Sum).Sum * $Rates.$Currency.$_) $($_)" }) -join " = ")"
}
#Display exchange rates
$ExchangeRates = "Exchange Rates: $(($Config.Currency | Where-Object {$Rates.$Currency.$_} | ForEach-Object { "$($Rates.$Currency.$_) $($_)" }) -join " = ")"
Write-Host $ExchangeRates
if ($API) {
#Update ExchangeRates, CurrentEarning and CurrentProfit in API
$API.ExchangeRates = $ExchangeRates
if ($RunningMiners -and $Rates.BTC.$FirstCurrency) {
if ($MinersNeedingBenchmark -or $MinersNeedingPowerUsageMeasurement) {
$API.CurrentEarning = "Current Earning per day: N/A (Benchmarking)"; $API.CurrentProfit = "Current Profit per day: N/A (Benchmarking)"
}
else {
$API.MiningCost = "N/A"
if ($MiningEarning) {
$API.CurrentEarning = "Current Earning per day: $(($Rates.BTC | Get-Member -MemberType NoteProperty -ErrorAction Ignore | Select-Object -ExpandProperty Name | ForEach-Object { "$_ $(((($RunningMiners | Measure-Object -Sum -Property Earning).Sum) * $Rates.BTC.$_).ToString("N$((($Rates.BTC.$FirstCurrency).ToString().split('.') | Select-Object -Index 0).Length)"))" }) -join ' = ')"
}
if ($PowerPrice) {
if ($Config.ShowPowerCost) {
$API.MiningCost = "Current Power Cost per day: MiningCost"
}
if ($MiningProfit) {
$API.CurrentProfit = "Current Profit per day: $(($Rates.BTC | Get-Member -MemberType NoteProperty -ErrorAction Ignore | Select-Object -ExpandProperty Name | ForEach-Object { "$_ $(((($RunningMiners | Measure-Object -Sum -Property Profit).Sum) * $Rates.BTC.$_).ToString("N$((($Rates.BTC.$FirstCurrency).ToString().split('.') | Select-Object -Index 0).Length)"))" }) -join ' = ')"
}
}
else { $API.CurrentProfit = "N/A" }
}
}
else { $API.CurrentEarning = ""; $API.CurrentProfit = "" }
}
if ($MinersNeedingBenchmark.Count -eq 0 -and $MinersNeedingPowerUsageMeasurement.Count -eq 0) {
if ($MiningEarning -lt $MiningCost) {
#Mining causes a loss
Write-Host -BackgroundColor Yellow -ForegroundColor Black "Mining is currently NOT profitable and causes a loss of $FirstCurrency $(($MiningEarning - $MiningCost).ToString("N$((Get-Culture).NumberFormat.CurrencyDecimalDigits)"))/day (Earning: $($MiningEarning.ToString("N$((Get-Culture).NumberFormat.CurrencyDecimalDigits)"))/day; Cost: $($MiningCost.ToString("N$((Get-Culture).NumberFormat.CurrencyDecimalDigits)"))/day$(if ($Config.BasePowerUsage) { "; base power cost of $FirstCurrency $(($BasePowerCost * $Rates.BTC.$FirstCurrency).ToString("N$((Get-Culture).NumberFormat.CurrencyDecimalDigits)"))/day for $($Config.BasePowerUsage)W is included in the calculation" })). "
}
if (($MiningEarning - $MiningCost) -lt $Config.ProfitabilityThreshold) {
#Mining profit is below the configured threshold
Write-Host -BackgroundColor Yellow -ForegroundColor Black "Mining profit is below the configured threshold of $FirstCurrency $($Config.ProfitabilityThreshold.ToString("N$((Get-Culture).NumberFormat.CurrencyDecimalDigits)"))/day; mining is suspended until threshold is reached."
}
}
#Read hash rate info from miners as to not overload the APIs and display miner download status
if ($Intervals.Count -eq 0 -or $MinersNeedingBenchmark -or $MinersNeedingPowerUsageMeasurement) {
#Enforce full benchmark interval time on first (benchmark) loop
$StatEnd = (Get-Date).ToUniversalTime().AddSeconds($Config.BenchmarkInterval)
$StatSpan = New-TimeSpan $StatStart $StatEnd
}
Write-Log "Start waiting before next run. "
$PollStart = (Get-Date).ToUniversalTime()
$PollEnd = $PollStart
$ExpectedHashRateSamples = 1
Do {
if ($Downloader) { $Downloader | Receive-Job -ErrorAction SilentlyContinue }
$RunningMiners | Where-Object { $_.GetStatus() -eq "Running" } | Sort-Object { @($_.Data | Sort-Object Date) } | ForEach-Object {
$Miner = $_
if (($Miner.Data | Where-Object Date -GT $PollStart).Count -lt $ExpectedHashRateSamples -and ($Miner.Data | Where-Object Date -GT $PollStart).Count -lt $Config.HashRateSamplesPerInterval) {
$Miner_Data = $Miner.UpdateMinerData()
$Sample = $Miner.Data | Where-Object Date -GE $PollEnd | Select-Object -last 1
if ($Sample) {
Write-Log -Level Verbose "$($Miner.Name) data sample retrieved: [$(($Sample.Hashrate.PSObject.Properties.Name | ForEach-Object { "$_ = $(($Sample.Hashrate.$_ | ConvertTo-Hash) -replace ' ')$(if ($Miner.AllowedBadShareRatio) { ", Shares Total = $($Sample.Shares.$_[2]), Rejected = $($Sample.Shares.$_[1])" })" }) -join '; ')$(if ($Sample.PowerUsage) { " / Power = $($Sample.PowerUsage.ToString("N2"))W" })]"
}
elseif ($Miner.WarmupTime -and (Get-Date).ToUniversalTime().AddSeconds(- $Miner.WarmupTime) -gt $PollStart -and -not @($Miner.Data | Where-Object Date -GT (Get-Date).ToUniversalTime().AddSeconds(- $Miner.WarmupTime))) {
#No data samples received for more than $warmup seconds, set miner idle
#Pre miner stop exec
$Command = ($ExecutionContext.InvokeCommand.ExpandString((Get-PrePostCommand -Miner $Miner -Config $Config -Event "PreStop"))).Trim()
if ($Command) { Start-PrePostCommand -Command $Command -Event "PreStop" }
Remove-Variable Command
$Miner.StatusMessage = " was stopped because MPM could not retrieve hash rate information from the miner API within $($Miner.WarmupTime) seconds"
$Miner.SetStatus("Idle")