PSMQTT.psm1

#Region '.\prefix.ps1' -1

# The content of this file will be prepended to the top of the psm1 module file. This is useful for custom module setup is needed on import.

# Add M2Mqtt dependency library
Add-Type -Path "$PSScriptRoot\Include\M2Mqtt.Net.dll"

# Import custom formatting
Update-FormatData -AppendPath "$PSScriptRoot\Include\PSMQTT.Format.ps1xml"
#EndRegion '.\prefix.ps1' 8
#Region '.\Classes\PSMQTTMessage.class.ps1' -1

class PSMQTTMessage
{
    [string]$Topic
    [string]$Payload
    [byte[]]$PayloadUTF8ByteA
    [datetime]$Timestamp
    [boolean]$DupFlag
    [int]$QosLevel
    [boolean]$Retain

    PSMQTTMessage(
        [string]$Topic,
        [string]$Payload
    )
    {
        $this.Topic = $Topic
        $this.Payload = $Payload
        $this.PayloadUTF8ByteA = [System.Text.Encoding]::UTF8.GetBytes($Payload)
        $this.Timestamp = (Get-Date)
    }

    PSMQTTMessage(
        [System.Management.Automation.PSEventArgs]$EventObject
    )
    {
        $this.Topic = $EventObject.SourceEventArgs.Topic
        $this.Payload = [System.Text.Encoding]::ASCII.GetString($EventObject.SourceEventArgs.Message)
        $this.PayloadUTF8ByteA = $EventObject.SourceEventArgs.Message
        $this.DupFlag = $EventObject.SourceEventArgs.DupFlag
        $this.QosLevel = $EventObject.SourceEventArgs.QosLevel
        $this.Retain = $EventObject.SourceEventArgs.Retain
        $this.Timestamp = $EventObject.TimeGenerated
    }

    [string] ToString ()
    {
        return ($this.Topic + ';' + $this.Payload)
    }

}
#EndRegion '.\Classes\PSMQTTMessage.class.ps1' 41
#Region '.\Private\Assert-FolderExist.ps1' -1

function Assert-FolderExist
{
    <#
    .SYNOPSIS
        Verify and create folder
    .DESCRIPTION
        Verifies that a folder path exists, if not it will create it
    .PARAMETER Path
        Defines the path to be validated
    .EXAMPLE
        'C:\Temp' | Assert-FolderExist
 
        This will verify that the path exists and if it does not the folder will be created
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [string]
        $Path
    )

    process
    {
        $exists = Test-Path -Path $Path -PathType Container
        if (!$exists)
        {
            $null = New-Item -Path $Path -ItemType Directory
        }
    }
}
#EndRegion '.\Private\Assert-FolderExist.ps1' 31
#Region '.\Private\Invoke-GarbageCollect.ps1' -1

function Invoke-GarbageCollect
{
    <#
    .SYNOPSIS
        Calls system.gc collect method. Purpose is mainly for readability.
    .DESCRIPTION
        Calls system.gc collect method. Purpose is mainly for readability.
    .EXAMPLE
        Invoke-GarbageCollect
    #>

    [system.gc]::Collect()
}
#EndRegion '.\Private\Invoke-GarbageCollect.ps1' 13
#Region '.\Public\Connect-MQTTBroker.ps1' -1

function Connect-MQTTBroker
{
    <#
      .DESCRIPTION
      This function establishes a session with the MQTT broker and returns a session object that should then be passed along when using other cmdlets.
 
      .EXAMPLE
      $Session = Connect-MQTTBroker -Hostname mqttbroker.contoso.com -Port 1234 -Username mqttuser -Password (ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force)
 
      Establish a MQTT Broker session with authentication
 
      .EXAMPLE
      $Session = Connect-MQTTBroker -Hostname mqttbroker.contoso.com -Port 1234
 
      Establish a MQTT Broker session without authentication
 
      .PARAMETER Hostname
      Defines the hostname of the MQTT Broker to connect to
 
      .PARAMETER Port
      Defines the port of the MQTT Broker. Defaults to 1883.
 
      .PARAMETER Username
      Defines the username to use when connecting to the MQTT broker
 
      .PARAMETER Password
      Defines the password (securestring) to use when connecting to the MQTT Broker
 
      .PARAMETER TLS
      Defines if the connection should be encrypted
 
    #>

    [cmdletBinding(DefaultParameterSetName = 'Anon')]
    param(
        [Parameter(Mandatory)]
        [string]
        $Hostname,

        [Parameter()]
        [int]
        $Port,

        [Parameter(Mandatory, ParameterSetName = 'Auth')]
        [string]
        $Username,

        [Parameter(Mandatory, ParameterSetName = 'Auth')]
        [securestring]
        $Password,

        [Parameter()]
        [switch]
        $TLS
    )

    #TODO implement additional connection properties like certificate ssl options etc. Different default ports based on

    # Use default ports if none are specified
    if (-not $PSBoundParameters['Port'])
    {
        if ($TLS)
        {
            $Port = 1884
        }
        else
        {
            $Port = 1883
        }
    }

    $MqttClient = New-Object -TypeName uPLibrary.Networking.M2Mqtt.MqttClient -ArgumentList $Hostname, $Port, $TLS, $null, $null, 'None'

    switch ($PSCmdlet.ParameterSetName)
    {
        'Anon'
        {
            $null = $MqttClient.Connect([guid]::NewGuid())
        }
        'Auth'
        {
            $null = $MqttClient.Connect([guid]::NewGuid(), $Username, (([pscredential]::New($Username, $Password)).GetNetworkCredential().Password))
        }
    }
    return $MqttClient

}
#EndRegion '.\Public\Connect-MQTTBroker.ps1' 87
#Region '.\Public\Disconnect-MQTTBroker.ps1' -1

function Disconnect-MQTTBroker
{
    <#
      .DESCRIPTION
      This function will disconnect a MQTTBroker session
 
      .EXAMPLE
      Disconnect-MQTTBroker -Session $Session
 
      .PARAMETER Session
      Defines the MQTTBroker session to use
 
    #>

    [cmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [uPLibrary.Networking.M2Mqtt.MqttClient]
        $Session
    )

    $Session.Disconnect()
}
#EndRegion '.\Public\Disconnect-MQTTBroker.ps1' 23
#Region '.\Public\Send-MQTTMessage.ps1' -1

function Send-MQTTMessage
{
    <#
      .DESCRIPTION
      This function will publish a message to a MQTT topic.
 
      .EXAMPLE
      Send-MQTTMEssage -Session $Session -Topic 'foo' -Payload '{"attribute":"value"}'
 
      .PARAMETER Session
      Defines the MQTTBroker session to use
 
      .PARAMETER Topic
      Defines the topic to publish the message to
 
      .PARAMETER Payload
      Defines the message as a string
 
      .PARAMETER Quiet
      Defines that messages are sent without outputing any objects.
 
    #>

    [cmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [uPLibrary.Networking.M2Mqtt.MqttClient]
        $Session,

        [Parameter(Mandatory)]
        [string]
        $Topic,

        [Parameter()]
        [string]
        $Payload,

        [Parameter()]
        [switch]
        $Quiet
    )

    try
    {
        $Message = [PSMQTTMessage]::New($Topic, $Payload)

        # Publish message to MQTTBroker
        $null = $Session.Publish($Message.Topic, $Message.PayloadUTF8ByteA)

        # Return object unless quiet
        if (-not $Quiet)
        {
            return $Message
        }
    }
    catch
    {
        $_
    }
}
#EndRegion '.\Public\Send-MQTTMessage.ps1' 60
#Region '.\Public\Watch-MQTTTopic.ps1' -1

function Watch-MQTTTopic
{
    <#
      .DESCRIPTION
      This function will publish a message to a MQTT topic.
 
      .EXAMPLE
      Watch-MQTTTopic -Session $Session -Topic "topic/#"
 
      .PARAMETER Session
      Defines the MQTTBroker session to use
 
      .PARAMETER Topic
      Defines the topic to publish the message to
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Deliberate use of WH. Function still returns objects. This write host is only used to indicate that the function is listening for events. Its rather important that such a status message is NOT picked up by the pipeline.')]
    [cmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [uPLibrary.Networking.M2Mqtt.MqttClient]
        $Session,

        [Parameter(Mandatory)]
        [string]
        $Topic
    )

    PROCESS
    {
        try
        {
            $SourceIdentifier = [guid]::NewGuid()

            Register-ObjectEvent -InputObject $Session -EventName MqttMsgPublishReceived -SourceIdentifier $SourceIdentifier

            $null = $Session.Subscribe($Topic, 0)

            Write-Host 'Listening...' -ForegroundColor Cyan

            while ($Session.IsConnected -and (Get-EventSubscriber -SourceIdentifier $SourceIdentifier))
            {
                try
                {
                    Get-Event -SourceIdentifier $SourceIdentifier -ErrorAction Stop | ForEach-Object {
                        [PSMQTTMessage]::New($PSItem)
                        Remove-Event -EventIdentifier $PSItem.EventIdentifier
                    }
                }
                catch [ArgumentException]
                {
                    if ($_.Exception.message -eq "Event with source identifier '$SourceIdentifier' does not exist.")
                    {
                        Start-Sleep -Milliseconds 100
                    }
                }
                catch
                {
                    throw $_
                }
            }
        }
        catch
        {
            $_
        }
        finally
        {
            $null = $Session.Unsubscribe($Topic)
            Unregister-Event -SourceIdentifier $SourceIdentifier
        }
    }
}
#EndRegion '.\Public\Watch-MQTTTopic.ps1' 74
#Region '.\suffix.ps1' -1

# The content of this file will be appended to the top of the psm1 module file. This is useful for custom procesedures after all module functions are loaded.
#EndRegion '.\suffix.ps1' 2