param( [Parameter(Mandatory=$true)] [string]$IpAddress, [string]$MinerId = "" ) $ConfigPath = "C:\MinerOpsAgent\agent-config.json" function Read-AgentConfig { if (!(Test-Path $ConfigPath)) { throw "Agent config not found: $ConfigPath" } return Get-Content $ConfigPath -Raw | ConvertFrom-Json } function Invoke-MinerCommandRaw { param( [string]$IpAddress, [string]$Command, [int]$Port = 4028, [int]$TimeoutMs = 2500 ) $client = $null try { $client = New-Object System.Net.Sockets.TcpClient $async = $client.BeginConnect($IpAddress, $Port, $null, $null) if (-not $async.AsyncWaitHandle.WaitOne($TimeoutMs)) { return "" } $client.EndConnect($async) $client.ReceiveTimeout = $TimeoutMs $client.SendTimeout = $TimeoutMs $stream = $client.GetStream() $payload = [System.Text.Encoding]::ASCII.GetBytes("{`"cmd`":`"$Command`"}`n") $stream.Write($payload, 0, $payload.Length) try { $client.Client.Shutdown([System.Net.Sockets.SocketShutdown]::Send) } catch { } $buffer = New-Object byte[] 262144 $chunks = New-Object System.Collections.ArrayList $started = Get-Date while (((Get-Date) - $started).TotalMilliseconds -lt $TimeoutMs) { if (-not $stream.DataAvailable) { Start-Sleep -Milliseconds 20 continue } $read = $stream.Read($buffer, 0, $buffer.Length) if ($read -le 0) { break } for ($i = 0; $i -lt $read; $i++) { [void]$chunks.Add([byte]$buffer[$i]) } if ($chunks.Count -gt 2097152) { break } } return [System.Text.Encoding]::UTF8.GetString([byte[]]$chunks.ToArray([byte])).Trim() } catch { return "" } finally { if ($client -ne $null) { $client.Close() } } } function Try-ParseJson { param([string]$Raw) if (!$Raw -or $Raw.Trim().Length -eq 0) { return $null } try { return $Raw | ConvertFrom-Json } catch { return $null } } function Find-FirstValue { param( [object]$Object, [string[]]$Names ) if ($null -eq $Object) { return "" } $json = $Object | ConvertTo-Json -Depth 80 -Compress foreach ($name in $Names) { $escaped = [regex]::Escape($name) $pattern = '"' + $escaped + '"\s*:\s*"([^"]+)"' $m = [regex]::Match($json, $pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) if ($m.Success) { return $m.Groups[1].Value } $pattern2 = '"' + $escaped + '"\s*:\s*([0-9A-Za-z\.\-_:]+)' $m2 = [regex]::Match($json, $pattern2, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) if ($m2.Success) { return $m2.Groups[1].Value } } return "" } function Find-MacAddress { param([string]$Text) if (!$Text) { return "" } $m = [regex]::Match($Text, '([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}') if ($m.Success) { return $m.Value.Replace("-", ":").ToUpper() } return "" } $config = Read-AgentConfig $hubUrl = ([string]$config.hub_url).TrimEnd("/") $secret = [string]$config.agent_secret $agentId = [string]$config.agent_id $commands = @( "summary", "devs", "edevs", "stats", "pools", "devdetails", "version", "get_version", "get_miner_info" ) $raws = [ordered]@{} $parsed = [ordered]@{} foreach ($cmd in $commands) { Write-Host "Querying $IpAddress command: $cmd" $raw = Invoke-MinerCommandRaw -IpAddress $IpAddress -Command $cmd -TimeoutMs 2500 $raws[$cmd] = $raw $parsed[$cmd] = Try-ParseJson -Raw $raw Write-Host " bytes: $($raw.Length)" } $allText = ($raws.GetEnumerator() | ForEach-Object { $_.Value }) -join "`n" $allParsed = [ordered]@{ summary = $parsed["summary"] devs = $parsed["devs"] edevs = $parsed["edevs"] stats = $parsed["stats"] pools = $parsed["pools"] devdetails = $parsed["devdetails"] version = $parsed["version"] get_version = $parsed["get_version"] get_miner_info = $parsed["get_miner_info"] } $mac = Find-FirstValue -Object $allParsed -Names @("MAC", "Mac", "mac", "macaddr", "mac_address", "Net MAC", "ethaddr", "Ethernet MAC") if (!$mac) { $mac = Find-MacAddress -Text $allText } $hostname = Find-FirstValue -Object $allParsed -Names @("Hostname", "hostname", "host", "Host") $minerSn = Find-FirstValue -Object $allParsed -Names @("Miner SN", "miner_sn", "miner sn", "Serial", "serial", "SN", "sn", "M_Serial") $firmware = Find-FirstValue -Object $allParsed -Names @("Firmware", "firmware", "firmware_version", "Soft Version", "Version", "CompileTime") $apiVersion = Find-FirstValue -Object $allParsed -Names @("API", "api", "api_version", "Api Version") $platform = Find-FirstValue -Object $allParsed -Names @("Platform", "platform", "Machine", "machine") $model = Find-FirstValue -Object $allParsed -Names @("Model", "model", "Miner Type", "miner_type", "Type", "type") if (!$model) { $model = "WhatsMiner" } $payload = [ordered]@{ ip_address = $IpAddress miner_id = $MinerId mac_address = $mac hostname = $hostname miner_sn = $minerSn model = $model miner_type = $model api_version = $apiVersion firmware_version = $firmware platform = $platform raw_version = @{ version = $raws["version"] get_version = $raws["get_version"] } raw_miner_info = @{ devs = $raws["devs"] edevs = $raws["edevs"] stats = $raws["stats"] pools = $raws["pools"] devdetails = $raws["devdetails"] get_miner_info = $raws["get_miner_info"] } } Write-Host "" Write-Host "Detected:" Write-Host " MAC: $mac" Write-Host " Hostname: $hostname" Write-Host " SN: $minerSn" Write-Host " Model: $model" Write-Host " Firmware: $firmware" Write-Host " API: $apiVersion" Write-Host " Platform: $platform" Write-Host "" $response = Invoke-RestMethod ` -Method Post ` -Uri "$hubUrl/api/agent/miner-enrichment-v3" ` -Headers @{ "X-MinerOps-Agent-ID" = $agentId "X-MinerOps-Agent-Secret" = $secret } ` -ContentType "application/json" ` -Body ($payload | ConvertTo-Json -Depth 80) $response | ConvertTo-Json -Depth 20