#
# prep
# install powershell 7
# winget install --id Microsoft.Powershell --source winget
# allow script execution
# Set-ExecutionPolicy -ExecutionPolicy Unrestricted
# VSCode should show version 7
# $PSVersionTable
Clear-Host
$myid = get-wmiobject Win32_ComputerSystemProduct | Select-Object -ExpandProperty UUID;
$verbose = $true;
$sleep_ms = 1000;
$mqtt_broker = "test.mosquitto.org";
$mqtt_clientid = New-Guid;
Write-Warning "My ID = '$($myid)'.";
# Create working folder
$working_path = "$($HOME)\hwinfo_adapter";
Write-Host "Creating working folder '$($working_path)'.";
New-Item -Path $working_path -ItemType Directory -ErrorAction SilentlyContinue;
Set-Location -Path $working_path;
# Download Nuget
$nuget_bin = "$($working_path)\nuget.exe";
$nuget_uri = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe";
if((Test-Path -Path $nuget_bin -PathType Leaf) -eq $false) {
Write-Host "Downloading Nuget binary.";
$progressPreference = 'silentlyContinue';
Invoke-WebRequest -Uri $nuget_uri -OutFile $nuget_bin;
$progressPreference = 'continue';
}
# Install MQTT library
$mqtt_bin = "$($working_path)\MQTTnet.4.1.0.247\lib\net6.0\MQTTnet.dll";
$mqtt_install = "$($working_path)\nuget.exe install MQTTnet -Version 4.1.0.247 -o $working_path";
if((Test-Path -Path $mqtt_bin -PathType Leaf) -eq $false) {
Write-Host "Installing MQTT library.";
Invoke-Expression $mqtt_install | Out-Null;
}
Add-Type -Path $mqtt_bin;
# Define sources for hwinfo adapter build
$source_base = "https://raw.githubusercontent.com/kallex/PromDapterHWiNFO/master";
$sources = [ordered]@{
source = @{
uri = "$($source_base)/PromDapterDeclarations/Source.cs";
output = "PromDapterDeclarations.Source.dll";
};
data_value = @{
uri = "$($source_base)/PromDapterDeclarations/DataValue.cs";
output = "PromDapterDeclarations.DataValue.dll";
};
data_item = @{
uri = "$($source_base)/PromDapterDeclarations/DataItem.cs";
output = "PromDapterDeclarations.DataItem.dll";
};
service_iface = @{
uri = "$($source_base)/PromDapterDeclarations/IPromDapterService.cs";
output = "PromDapterDeclarations.IPromDapterService.dll";
};
service_impl = @{
uri = "$($source_base)/HWiNFODataProvider/HWiNFOProvider.cs";
output = "SensorMonHTTP.HWiNFOProvider.dll";
};
};
# .NET references to include when building
$references = [System.Collections.ArrayList]@(
"System.Core",
"System.Collections",
"System.IO.MemoryMappedFiles",
"System.Linq");
# Download and compile each source
Write-Host "Compiling sources.";
foreach ($key in $sources.Keys)
{
$item = $sources[$key];
Write-Host "`t'$($key)': Downloading.";
$response = Invoke-WebRequest -Uri $item.uri -SkipHttpErrorCheck;
if ($response.StatusCode -ne 200) {
Write-Error "Unable to download '$($item.uri)'."
exit;
}
$source = $response.Content;
Write-Host "`t'$($key)': Compiling.";
$path = Join-Path -Path $(Get-Location) -ChildPath $item.output;
Remove-Item -Path $path -ErrorAction SilentlyContinue;
try {
Add-Type -TypeDefinition $source `
-Language CSharp `
-OutputAssembly $path `
-OutputType Library `
-ReferencedAssemblies $references `
-IgnoreWarnings -PassThru -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null;
} catch {
Write-Error $_.Exception.Message;
exit;
}
# Add compiled source as a reference for the next source
$references.Add($path) | Out-Null;
}
# Create hwinfo adapter instance
Write-Host "Creating instance.";
$wrapper = New-Object -TypeName SensorMonHTTP.HWiNFOProvider;
# Open hwinfo shared memory
Write-Host "Opening HWINFO shared memory.";
if($wrapper.Open.Invoke().IsFaulted -eq $true) {
Write-Error "Unable to access HWINFO shared memory.";
exit;
}
# Create and connect MQTT client
Write-Host "Creating MQTT client.";
$mqtt_factory = [MQTTnet.MqttFactory]::new();
$mqtt_client = $mqtt_factory.CreateMqttClient();
$mqtt_options = [MQTTnet.Client.MqttClientOptionsBuilder]::new(). `
WithTcpServer($mqtt_broker). `
WithCleanSession($true). `
WithClientId($mqtt_clientid). `
Build();
try {
$mqtt_client.ConnectAsync($mqtt_options).GetAwaiter().GetResult() | Out-Null ;
} catch {
Write-Error "MQTT connection failed.";
exit;
}
# Define function to combine data item source and name into a MQTT-able topic
function build_metric {
param( $data_item )
# remove leading and trailing spaces
# remove forward slashes
# remove dollar signs
# join array with forward slash
# remove trailing slash
$src = $data_item.Source `
-split ':' `
-replace '^\s+|\s+$' `
-replace '/' `
-replace '$' `
-join '/' `
-replace '/$';
# remove forward slashes
$name = $data_item.Name `
-replace '/';
# remove pound signs
# remove periods
# remove asterisk
# replace plus with underscore
$metric = "hwinfo/$($myid)/$($src)/$($name)" `
-replace '#' `
-replace '\.' `
-replace '\*' `
-replace '\+','_';
return $metric
};
# Run data collection loop
$last_known_mqdata = @{};
Write-Host "Terminate with 'x'.";
while($true)
{
if($verbose -eq $true) {
Write-Host "Retrieving HWINFO shared memory.";
}
$data = $wrapper.GetDataItems.Invoke().GetAwaiter().GetResult();
# Create flat data item view that can be displayed by powershell
$psdata = $data | Select-Object `
@{Name='Source';Expression={$_.Source.SourceName};}, `
Name,
@{Name='Value';Expression={$_.Value.Object};}, `
Unit, `
@{Name='Category';Expression={$_.Category};}, `
Timestamp;
#$psdata | Format-Table
#$psdata | Out-GridView
# TODO: publish changed metrics only
# Transform data items for MQTT
$current_mqdata = $psdata | Select-Object `
@{Name='Topic';Expression={ build_metric($_) }}, `
@{Name='Payload';Expression={
@{
category=$_.Category;
value=[math]::Round($_.Value, 3);
unit=$_.Unit;
ts=Get-Date -UFormat %s
}
}};
# Publish to MQTT broker
if($verbose -eq $true) {
Write-Host "Publishing metrics.";
}
$pub_count = 0;
foreach ($metric in $current_mqdata) {
$publish = $false;
$publish_reason = "";
$find = $last_known_mqdata | Where-Object { $_.Topic -eq $metric.Topic };
if ($find.Count -eq 0) {
$publish = $true;
$publish_reason = "new metric";
}
elseif ($find[0].Payload.value -ne $metric.Payload.value) {
$publish = $true;
$publish_reason = "value changed, old value = '$($find[0].Payload.value)'";
}
if($publish -eq $true) {
$pub_count += 1;
if($verbose -eq $true) {
Write-Host "Publishing '$($metric.Topic)' = '$($metric.Payload.value)' reason: $($publish_reason).";
}
$message = [MQTTnet.MqttApplicationMessageBuilder]::new(). `
WithTopic($metric.Topic). `
WithPayload(($metric.Payload | ConvertTo-Json -Depth 4)). `
WithRetainFlag(). `
Build();
$mqtt_client.PublishAsync($message).GetAwaiter().GetResult() | Out-Null;
}
}
# TODO: verify this works correctly
$last_known_mqdata = $current_mqdata.Clone();
Write-Host "Published '$($pub_count)' metrics.";
# Check if we should terminate loop
if ([console]::KeyAvailable)
{
$key = [system.console]::readkey($true)
if ($key.key -eq "x")
{
Write-Host "Terminated by user."
break;
}
}
Start-Sleep -Milliseconds $sleep_ms;
}
# Cleanup
Start-Sleep 1;
Write-Host "Closing HWINFO shared memory.";
$wrapper.Close.Invoke().GetAwaiter().GetResult() | Out-Null;
Write-Host "Disconnecting MQTT client.";
$mqtt_disc_options = [MQTTnet.Client.MqttClientDisconnectOptionsBuilder]::new().Build();
$mqtt_client.DisconnectAsync($mqtt_disc_options).GetAwaiter().GetResult() | Out-Null;