SlackBot.psm1

#Invokes an instance of a bot
Function Invoke-SlackBot {
        <#
        .SYNOPSIS
            Start a Slack Bot.
 
        .DESCRIPTION
            Starts a long running script which connects to the Slack API via websockets to listen to and
            respond to Slack messages.
 
        .PARAMETER Token
            Your token for connecting to the Slack API.
 
        .PARAMETER LogPath
            A path where activity logs should be written, default: Logs/SlackBot.log in the current user's profile folder.
 
        .PARAMETER PSSlackConfigPath
            Path to the configuration file for the PSSlack module.
 
        .EXAMPLE
            Invoke-SlackBot
 
            Starts the SlackBot which will look for Token.XML and PSSlackConfig.xml in the parent of the
            current directory for credentials to the Slack API and config for the PSSlack module.
    #>
 
    [cmdletbinding()]
    Param(
        [string]
        $Token = (Import-Clixml (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Token.xml')),  #So I don't accidentally put it on the internet

        [string]
        $LogPath = (Join-Path -Path (Join-Path -Path ([Environment]::GetFolderPath('UserProfile')) -ChildPath 'Logs') -ChildPath 'SlackBot.log'),

        [string]
        $PSSlackConfigPath = (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'PSSlackConfig.xml')
    )
    
    Set-PSSlackConfig -Path $PSSlackConfigPath -Token $Token
    
    #Web API call starts the session and gets a websocket URL to use.
    $RTMSession = Invoke-RestMethod -Uri https://slack.com/api/rtm.start -Body @{token="$Token"}
    Write-Log "I am $($RTMSession.self.name)" -Path $LogPath

    Try{
        Do{
            $WS = New-Object System.Net.WebSockets.ClientWebSocket                                                
            $CT = New-Object System.Threading.CancellationToken                                                   

            $Conn = $WS.ConnectAsync($RTMSession.URL, $CT)                                                  
            While (!$Conn.IsCompleted) { Start-Sleep -Milliseconds 100 }

           Write-Log "Connected to $($RTMSession.URL)" -Path $LogPath

            $Size = 1024
            $Array = [byte[]] @(,0) * $Size
            $Recv = New-Object System.ArraySegment[byte] -ArgumentList @(,$Array)

            While ($WS.State -eq 'Open') {

                $RTM = ""

                Do {
                    $Conn = $WS.ReceiveAsync($Recv, $CT)
                    While (!$Conn.IsCompleted) { Start-Sleep -Milliseconds 100 }

                    $Recv.Array[0..($Conn.Result.Count - 1)] | ForEach-Object { $RTM = $RTM + [char]$_ }

                } Until ($Conn.Result.Count -lt $Size)

                Write-Log "$RTM" -Path $LogPath

                If ($RTM){
                    $RTM = ($RTM | convertfrom-json)

                    Switch ($RTM){
                        {($_.type -eq 'message') -and (!$_.reply_to)} { 

                            If ( ($_.text -Match "<@$($RTMSession.self.id)>") -or $_.channel.StartsWith('D') ){
                                #A message was sent to the bot

                                # *** Responses go here, for example..***
                                $words = "$($_.text)".ToLower()
                                while ($words -match ' '){
                                    $words = $words -replace ' ',' '
                                }
                                $words = $words -split ' '
                                
                                Switch ($words){
                                    {@("hey","hello","hi") -contains $_} { Send-SlackMsg -Text 'Hello!' -Channel $RTM.Channel }
                                    {@("bye","cya") -contains $_} { Send-SlackMsg -Text 'Goodbye!' -Channel $RTM.Channel }

                                    default { Write-Verbose "I have no response for $_" }
                                }

                            }Else{
                                Write-Log "Message ignored as it wasn't sent to @$($RTMSession.self.name) or in a DM channel" -Path $LogPath
                            }
                        }
                        {$_.type -eq 'reconnect_url'} { $RTMSession.URL = $RTM.url }

                        default { Write-Log "No action specified for $($RTM.type) event" -Path $LogPath }            
                    }
                }
            }   
        } Until (!$Conn)

    }Finally{

        If ($WS) { 
            Write-Verbose "Closing websocket"
            $WS.Dispose()
        }

    }

}
Function Send-SlackMsg {
    <#
        .SYNOPSIS
            Sends simple message responses via the RTM API
 
        .DESCRIPTION
            Used for sending Slack Messages through the WebSocket that is established by Invoke-SlackBot.
 
        .PARAMETER Text
            The message text string to be sent.
 
        .PARAMETER Channel
            The name or ID of the channel to send the message to.
 
        .PARAMETER ID
            A unique ID for the message. The current datetime's tick is used by default.
 
        .PARAMETER Timeout
            The number of seconds before sending the message should timeout.
 
        .EXAMPLE
            Send-SlackMsg -Text 'Hello!' -Channel 12345
 
            Sends the specified text to the specified channel.
 
        .EXAMPLE
            Send-SlackMsg -Text 'Goodbye!' -Channel 12345 -ID 1 -Timeout 10
 
            Sends the specified text to the specified channel with specified ID and timeout settings.
    #>
 
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$Text,
        
        [Parameter(Mandatory = $true)]
        $Channel,
        
        $ID = (get-date).ticks,

        $Timeout = 30
    )
    
    If (!($WS -is [System.Net.WebSockets.ClientWebSocket])) {
        Write-Log  -Level Error 'A WebSocket to Slack is not open via $WS.' -Path $LogPath
        Return
    }

    $Prop = @{'id' = $ID;
        'type'     = 'message';
        'text'     = $Text;
        'channel'  = $Channel
    }
            
    $Msg = (New-Object -TypeName PSObject -Prop $Prop) | ConvertTo-Json
            
    $Array = @()
    $Encoding = [System.Text.Encoding]::UTF8
    $Array = $Encoding.GetBytes($Msg)
           
    $Msg = New-Object System.ArraySegment[byte]  -ArgumentList @(, $Array)

    $Conn = $WS.SendAsync($Msg, [System.Net.WebSockets.WebSocketMessageType]::Text, [System.Boolean]::TrueString, $CT)
    $ConnStart = Get-Date

    While (!$Conn.IsCompleted) { 
        $TimeTaken = ((get-date) - $ConnStart).Seconds
        If ($TimeTaken -gt $Timeout) {
            Write-Log -Level Error "Message $ID took longer than $Timeout seconds and may not have been sent." -Path $LogPath
            Return
        }
        Start-Sleep -Milliseconds 100 
    }
   
}
Function ConvertFrom-UnixTime {
<#
.SYNOPSIS
  ConvertFrom-UnixTime converts Unix timestamps to a PowerShell datetime object.
.PARAMETER UnixTime
  And integer representing the unix formatted time.
.EXAMPLE
  1489664257 | ConvertFrom-UnixTime
.EXAMPLE
  ConvertFrom-UnixTime -UnixTime 1489664257
.LINK
  https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0
#>
  
    Param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [Int32]$UnixTime
    )

    BEGIN {
        $StartDate = Get-Date -Date '01/01/1970'
    }
    PROCESS {
        $TimeSpan = New-Timespan -Seconds $UnixTime
        $StartDate + $TimeSpan
    }
}
Function Write-Log {
<#
.SYNOPSIS
   Write-Log writes a message to a specified log file with the current time stamp.
.DESCRIPTION
   The Write-Log function is designed to add logging capability to other scripts.
   In addition to writing output and/or verbose you can write to a log file for
   later debugging.
.PARAMETER Message
   Message is the content that you wish to add to the log file.
.PARAMETER Path
   The path to the log file to which you would like to write. By default the function will
   create the path and file if it does not exist.
.PARAMETER Level
   Specify the criticality of the log information being written to the log (i.e. Error, Warning, Informational)
.PARAMETER NoClobber
   Use NoClobber if you do not wish to overwrite an existing file.
.EXAMPLE
   Write-Log -Message 'Log message'
   Writes the message to PowerShellLog.log in the current user's profile folder.
.EXAMPLE
   Write-Log -Message 'Restarting Server.' -Path c:\Logs\Scriptoutput.log
   Writes the content to the specified log file and creates the path and file specified.
.EXAMPLE
   Write-Log -Message 'Folder does not exist.' -Path c:\Logs\Script.log -Level Error
   Writes the message to the specified log file as an error message, and writes the message to the error pipeline.
.LINK
   https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0
#>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [Alias("LogContent")]
        [string]$Message,

        [Alias('LogPath')]
        [string]$Path=(Join-Path -Path ([Environment]::GetFolderPath('UserProfile')) -ChildPath 'PowerShellLog.log'),
        
        [ValidateSet("Error","Warn","Info")]
        [string]$Level="Info",
        
        [switch]$NoClobber
    )

    BEGIN {
        $VerbosePreference = 'Continue'
    }
    PROCESS {
        
        If ((Test-Path $Path) -AND $NoClobber) {
            Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name." -ErrorAction Stop
        }
        ElseIf (!(Test-Path $Path)) {
            Write-Verbose "Creating $Path."
            New-Item $Path -Force -ItemType File | Out-Null
        }

        $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

        Switch ($Level) {
            'Error' {
                Write-Error $Message
                $LevelText = 'ERROR:'
            }
            'Warn' {
                Write-Warning $Message
                $LevelText = 'WARNING:'
            }
            'Info' {
                Write-Verbose $Message
                $LevelText = 'INFO:'
            }
        }
        
        "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append
    }
    END { }
}