diff --git a/Source/MQTTnet.PowerShell/Cmdlets/ConnectMqttSessionCmdlet.cs b/Source/MQTTnet.PowerShell/Cmdlets/ConnectMqttSessionCmdlet.cs index e576267b..8627baaa 100644 --- a/Source/MQTTnet.PowerShell/Cmdlets/ConnectMqttSessionCmdlet.cs +++ b/Source/MQTTnet.PowerShell/Cmdlets/ConnectMqttSessionCmdlet.cs @@ -12,13 +12,15 @@ public class ConnectMqttSessionCmdlet : PSCmdlet [Parameter] public string? ClientId { get; set; } = Guid.NewGuid().ToString(); - [Parameter(Mandatory = true)] - public required new string Host { get; set; } + [Parameter(Mandatory = true, ParameterSetName = "Tcp")] + [Parameter(Mandatory = true, ParameterSetName = "TcpWithTls")] + public new string? Host { get; set; } [Parameter] public string? Password { get; set; } - [Parameter] + [Parameter(ParameterSetName = "Tcp")] + [Parameter(ParameterSetName = "TcpWithTls")] public int Port { get; set; } = 1883; [Parameter(Mandatory = true, ValueFromPipeline = true)] @@ -27,19 +29,90 @@ public class ConnectMqttSessionCmdlet : PSCmdlet [Parameter] public string? Username { get; set; } - [Parameter] + [Parameter(ParameterSetName = "TcpWithTls")] public SwitchParameter UseTls { get; set; } + [Parameter(ParameterSetName = "TcpWithTls")] + public SwitchParameter AllowUntrustedCertificates { get; set; } + + [Parameter(Mandatory = true, ParameterSetName = "WebSocket")] + public string? WebSocketUri { get; set; } + + [Parameter] + public int KeepAlivePeriod { get; set; } = 15; + + [Parameter] + public int Timeout { get; set; } = 10; + + [Parameter] + public string? WillTopic { get; set; } + + [Parameter] + public string? WillPayload { get; set; } + + [Parameter] + public int WillQoS { get; set; } = 0; + + [Parameter] + public SwitchParameter WillRetain { get; set; } + protected override void ProcessRecord() { var clientOptionsBuilder = new MqttClientOptionsBuilder(); - clientOptionsBuilder.WithTcpServer(Host, Port); + + // Configure transport + if (ParameterSetName == "WebSocket") + { + clientOptionsBuilder.WithWebSocketServer(o => o.WithUri(WebSocketUri!)); + } + else + { + clientOptionsBuilder.WithTcpServer(Host!, Port); + + if (UseTls) + { + clientOptionsBuilder.WithTlsOptions(o => + { + if (AllowUntrustedCertificates) + { + o.WithCertificateValidationHandler(_ => true); + } + }); + } + } + // Configure authentication if (Username != null) { clientOptionsBuilder.WithCredentials(Username, Password); } + // Configure session + clientOptionsBuilder.WithCleanSession(CleanSession); + + if (!string.IsNullOrEmpty(ClientId)) + { + clientOptionsBuilder.WithClientId(ClientId); + } + + // Configure keep alive + clientOptionsBuilder.WithKeepAlivePeriod(TimeSpan.FromSeconds(KeepAlivePeriod)); + clientOptionsBuilder.WithTimeout(TimeSpan.FromSeconds(Timeout)); + + // Configure Will message + if (!string.IsNullOrEmpty(WillTopic)) + { + clientOptionsBuilder.WithWillTopic(WillTopic); + + if (!string.IsNullOrEmpty(WillPayload)) + { + clientOptionsBuilder.WithWillPayload(System.Text.Encoding.UTF8.GetBytes(WillPayload)); + } + + clientOptionsBuilder.WithWillQualityOfServiceLevel((Protocol.MqttQualityOfServiceLevel)WillQoS); + clientOptionsBuilder.WithWillRetain(WillRetain); + } + var response = Session.GetClient().ConnectAsync(clientOptionsBuilder.Build()).GetAwaiter().GetResult(); WriteObject(response); diff --git a/Source/MQTTnet.PowerShell/Cmdlets/GetMqttSessionStatusCmdlet.cs b/Source/MQTTnet.PowerShell/Cmdlets/GetMqttSessionStatusCmdlet.cs new file mode 100644 index 00000000..2603e3d6 --- /dev/null +++ b/Source/MQTTnet.PowerShell/Cmdlets/GetMqttSessionStatusCmdlet.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Management.Automation; + +namespace MQTTnet.PowerShell.Cmdlets; + +[Cmdlet(VerbsCommon.Get, "MqttSessionStatus")] +[OutputType(typeof(MqttClientConnectionStatus))] +public class GetMqttSessionStatusCmdlet : PSCmdlet +{ + [Parameter(Mandatory = true, ValueFromPipeline = true)] + public required PsMqttSession Session { get; set; } + + protected override void ProcessRecord() + { + var client = Session.GetClient(); + var status = new + { + IsConnected = client.IsConnected, + Options = client.Options + }; + + WriteObject(status); + } +} diff --git a/Source/MQTTnet.PowerShell/Cmdlets/PublishMqttMessageCmdlet.cs b/Source/MQTTnet.PowerShell/Cmdlets/PublishMqttMessageCmdlet.cs index 7547b25b..b44a9289 100644 --- a/Source/MQTTnet.PowerShell/Cmdlets/PublishMqttMessageCmdlet.cs +++ b/Source/MQTTnet.PowerShell/Cmdlets/PublishMqttMessageCmdlet.cs @@ -1,6 +1,8 @@ +using System.Collections; using System.Management.Automation; using System.Text; using MQTTnet.Protocol; +using MQTTnet.Packets; namespace MQTTnet.PowerShell.Cmdlets; @@ -22,29 +24,44 @@ public class PublishMqttMessageCmdlet : PSCmdlet [Parameter(Mandatory = true)] public required string Topic { get; set; } + [Parameter] public string? ContentType { get; set; } + [Parameter] public string? ResponseTopic { get; set; } + [Parameter] public ushort TopicAlias { get; set; } + [Parameter] public uint MessageExpiryInterval { get; set; } + [Parameter] + public Hashtable? UserProperties { get; set; } + protected override void ProcessRecord() { - // if (Session == null || !Session.IsConnected) - // throw new InvalidOperationException("Session not connected."); - - var msg = new MqttApplicationMessageBuilder().WithTopic(Topic) + var msgBuilder = new MqttApplicationMessageBuilder() + .WithTopic(Topic) .WithPayload(Encoding.UTF8.GetBytes(Payload ?? string.Empty)) .WithQualityOfServiceLevel((MqttQualityOfServiceLevel)QoS) .WithRetainFlag(Retain) .WithContentType(ContentType) .WithResponseTopic(ResponseTopic) .WithTopicAlias(TopicAlias) - .WithMessageExpiryInterval(MessageExpiryInterval) - .Build(); + .WithMessageExpiryInterval(MessageExpiryInterval); + // Add user properties if provided + if (UserProperties != null && UserProperties.Count > 0) + { + foreach (var key in UserProperties.Keys) + { + var value = UserProperties[key]?.ToString() ?? string.Empty; + msgBuilder.WithUserProperty(key.ToString()!, value); + } + } + + var msg = msgBuilder.Build(); var response = Session.GetClient().PublishAsync(msg).GetAwaiter().GetResult(); WriteObject(response); diff --git a/Source/MQTTnet.PowerShell/Cmdlets/RegisterMqttMessageHandlerCmdlet.cs b/Source/MQTTnet.PowerShell/Cmdlets/RegisterMqttMessageHandlerCmdlet.cs index 844e45b9..007d56fc 100644 --- a/Source/MQTTnet.PowerShell/Cmdlets/RegisterMqttMessageHandlerCmdlet.cs +++ b/Source/MQTTnet.PowerShell/Cmdlets/RegisterMqttMessageHandlerCmdlet.cs @@ -14,10 +14,43 @@ public class RegisterMqttMessageHandlerCmdlet : PSCmdlet protected override void ProcessRecord() { - EventHandler handler = (s, e) => + // Capture the ScriptBlock and current default runspace (available during cmdlet execution) + var scriptBlock = Action ?? throw new ArgumentNullException(nameof(Action)); + var capturedRunspace = Runspace.DefaultRunspace ?? throw new InvalidOperationException("No default runspace available during cmdlet execution."); + var capturedHost = Host; + + EventHandler handler = (sender, e) => { - //throw new NotImplementedException(); - //InvokeCommand.InvokeScript(Action, false, PipelineResultTypes.Output, null, new object[] { e.Topic, e.Payload }); + // Run asynchronously to avoid blocking the event thread + Task.Run(() => + { + // Temporarily set the default runspace for this thread + var originalRunspace = Runspace.DefaultRunspace; + Runspace.DefaultRunspace = capturedRunspace; + + try + { + // Invoke the ScriptBlock directly with arguments (returns Collection) + var results = scriptBlock.Invoke(e.Topic, e.Payload, e); // Pass topic, payload, and full message + + // Handle output manually (e.g., write to host/console) + foreach (var result in results) + { + capturedHost.UI.WriteLine(result?.ToString() ?? string.Empty); + } + } + catch (Exception ex) + { + // Handle errors by writing to host error stream + capturedHost.UI.WriteErrorLine($"MQTT handler error: {ex.Message}"); + // Optionally: capturedHost.UI.WriteVerboseLine(ex.StackTrace); for more details + } + finally + { + // Restore the original default runspace for this thread + Runspace.DefaultRunspace = originalRunspace; + } + }); }; Session.MessageReceived += handler; diff --git a/Source/MQTTnet.PowerShell/Cmdlets/UnregisterMqttMessageHandlerCmdlet.cs b/Source/MQTTnet.PowerShell/Cmdlets/UnregisterMqttMessageHandlerCmdlet.cs new file mode 100644 index 00000000..5ab91ced --- /dev/null +++ b/Source/MQTTnet.PowerShell/Cmdlets/UnregisterMqttMessageHandlerCmdlet.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Management.Automation; + +namespace MQTTnet.PowerShell.Cmdlets; + +[Cmdlet(VerbsLifecycle.Unregister, "MqttMessageHandler")] +public class UnregisterMqttMessageHandlerCmdlet : PSCmdlet +{ + [Parameter(Mandatory = true, ValueFromPipeline = true)] + public required PsMqttSession Session { get; set; } + + protected override void ProcessRecord() + { + // Clear all event handlers + Session.ClearMessageHandlers(); + WriteObject($"All message handlers cleared for MQTT session."); + } +} diff --git a/Source/MQTTnet.PowerShell/PsMqttSession.cs b/Source/MQTTnet.PowerShell/PsMqttSession.cs index 83089763..8ab3c0d2 100644 --- a/Source/MQTTnet.PowerShell/PsMqttSession.cs +++ b/Source/MQTTnet.PowerShell/PsMqttSession.cs @@ -38,6 +38,18 @@ public sealed class PsMqttSession : IDisposable public event EventHandler? MessageReceived; + public void ClearMessageHandlers() + { + // Remove all event subscribers + if (MessageReceived != null) + { + foreach (var handler in MessageReceived.GetInvocationList()) + { + MessageReceived -= (EventHandler)handler; + } + } + } + public void Dispose() { _client.Dispose(); diff --git a/Source/MQTTnet.PowerShell/Test.ps1 b/Source/MQTTnet.PowerShell/Test.ps1 index 985330ea..46afabe5 100644 --- a/Source/MQTTnet.PowerShell/Test.ps1 +++ b/Source/MQTTnet.PowerShell/Test.ps1 @@ -1,15 +1,117 @@ +# MQTTnet PowerShell Module Test Script +# This script demonstrates the usage of the MQTTnet PowerShell module + Import-Module ./bin/Debug/net8.0/MQTTnet.PowerShell.dll +Write-Host "=== MQTTnet PowerShell Module Test ===" -ForegroundColor Cyan + +# Example 1: Basic TCP connection +Write-Host "`n1. Testing basic TCP connection..." -ForegroundColor Yellow $session = New-MqttSession -Connect-MqttSession -Session $session -Host "192.168.1.16" +try { + Connect-MqttSession -Session $session -Host "broker.hivemq.com" -Port 1883 + Write-Host "Connected successfully!" -ForegroundColor Green + + # Get session status + Write-Host "`n2. Getting session status..." -ForegroundColor Yellow + $status = Get-MqttSessionStatus -Session $session + Write-Host "IsConnected: $($status.IsConnected)" -ForegroundColor Green + + # Example 2: Publish a simple message + Write-Host "`n3. Publishing a simple message..." -ForegroundColor Yellow + Publish-MqttMessage -Session $session -Topic "test/powershell" -Payload "Hello from PowerShell!" + Write-Host "Message published!" -ForegroundColor Green + + # Example 3: Publish with user properties + Write-Host "`n4. Publishing with user properties..." -ForegroundColor Yellow + $properties = @{ + "source" = "PowerShell" + "version" = "1.0" + } + Publish-MqttMessage -Session $session -Topic "test/powershell/advanced" -Payload "Advanced message" -UserProperties $properties -QoS 1 + Write-Host "Message with properties published!" -ForegroundColor Green + + # Example 4: Subscribe and receive messages + Write-Host "`n5. Subscribing to topics..." -ForegroundColor Yellow + Subscribe-MqttTopic -Session $session -Topic "test/demo" + Write-Host "Subscribed to test/demo" -ForegroundColor Green + + # Example 5: Register a message handler + Write-Host "`n6. Registering message handler..." -ForegroundColor Yellow + Register-MqttMessageHandler -Session $session -Action { + param($topic, $payload, $message) + Write-Host "Received message on topic: $topic" -ForegroundColor Cyan + Write-Host "Payload: $payload" -ForegroundColor White + Write-Host "QoS: $($message.QoS)" -ForegroundColor White + } + Write-Host "Handler registered!" -ForegroundColor Green + + # Publish a test message that the handler will receive + Write-Host "`n7. Publishing test message for handler..." -ForegroundColor Yellow + Publish-MqttMessage -Session $session -Topic "test/demo" -Payload "Test message for handler" + Start-Sleep -Seconds 2 + + # Example 6: Unsubscribe + Write-Host "`n8. Unsubscribing from topic..." -ForegroundColor Yellow + Unsubscribe-MqttTopic -Session $session -Topic "test/demo" + Write-Host "Unsubscribed!" -ForegroundColor Green + + # Example 7: Clean disconnect + Write-Host "`n9. Disconnecting..." -ForegroundColor Yellow + Disconnect-MqttSession -Session $session + Write-Host "Disconnected!" -ForegroundColor Green +} +catch { + Write-Host "Error: $_" -ForegroundColor Red +} +finally { + # Cleanup + Remove-MqttSession -Session $session + Write-Host "`nSession cleaned up!" -ForegroundColor Green +} -Publish-MqttMessage -Session $session -Topic "Test" -Payload "Hello from PowerShell" +Write-Host "`n=== WebSocket Connection Example ===" -ForegroundColor Cyan -Subscribe-MqttTopic -Session $session -Topic "test/demo" +# Example 8: WebSocket connection +Write-Host "`nTesting WebSocket connection..." -ForegroundColor Yellow +$wsSession = New-MqttSession -Receive-MqttMessage -Session $session -TimeoutSeconds 200 +try { + Connect-MqttSession -Session $wsSession -WebSocketUri "broker.hivemq.com:8000/mqtt" + Write-Host "Connected via WebSocket!" -ForegroundColor Green + + Publish-MqttMessage -Session $wsSession -Topic "test/websocket" -Payload "Hello via WebSocket!" + Write-Host "Message published via WebSocket!" -ForegroundColor Green + + Disconnect-MqttSession -Session $wsSession +} +catch { + Write-Host "WebSocket Error: $_" -ForegroundColor Red +} +finally { + Remove-MqttSession -Session $wsSession +} -Disconnect-MqttSession -Session $session -Remove-MqttSession -Session $session +Write-Host "`n=== Connection with Will Message Example ===" -ForegroundColor Cyan + +# Example 9: Connection with Last Will Testament +Write-Host "`nTesting connection with Will message..." -ForegroundColor Yellow +$willSession = New-MqttSession + +try { + Connect-MqttSession -Session $willSession -Host "broker.hivemq.com" ` + -WillTopic "test/will" -WillPayload "Client disconnected unexpectedly" -WillQoS 1 -WillRetain + Write-Host "Connected with Will message configured!" -ForegroundColor Green + + Disconnect-MqttSession -Session $willSession +} +catch { + Write-Host "Will Error: $_" -ForegroundColor Red +} +finally { + Remove-MqttSession -Session $willSession +} + +Write-Host "`n=== All tests completed ===" -ForegroundColor Cyan