Skip to content

ChatOps on Windows with Hubot and PowerShell

· 14 min

ChatOps is a term used to describe bringing development or operations work that is already happening in the background into a common chat room. It involves having everyone in the team in a single chat room, then bringing tools into the room so everyone can automate, collaborate and see how automation is used to solve problems. In doing so, you are unifying the communication about what work gets done and have a history of it happening.

ChatOps can be supplemented with the use of tools or scripts exposed using a chat bot. Users in the chat room can talk to the bot and have it take actions on their behalf, some examples of this may be:

Bots can also be a great way to expose functionality to low-privledged users such as help desk staff, without having to create web interfaces or forms.

If you want more details on the concept of ChatOps, I recommend watching ChatOps, a Beginners Guide presented by Jason Hand.

A popular toolset for ChatOps is Slack as the chat client, and Hubot as the bot. In this post we will use Slack and Hubot together with a PowerShell module I’ve written called PoshHubot. The module will handle installation and basic administration of Hubot. From there, we will integrate Hubot with PowerShell so we can perform some ChatOps in the Microsoft ecosystem.

Basic Hubot Concepts#

There are a few basic Hubot concepts I want to introduce to you before we continue.

Node.js and CoffeeScript#

Hubot is built in CoffeeScript, which is a programming language that complies into JavaScript. Hubot is built on Node.js. This means the server running the bot will need to have Node.js and CoffeeScript installed. The PoshHubot module will handle this.

When writing scripts for your bot, you will have to get your hands a little dirty with CoffeeScript. We will be calling PowerShell from inside CoffeeScript, so we only need to know a tiny bit to get by.

Environment Variables#

Hubot scripts make heavy use of environment variables to set certain options for the bot.

One example of this is to allow the Hubot to access sites with invalid SSL certificates, you would set an environment variable of NODE_TLS_REJECT_UNAUTHORIZED.

There are 3 possible ways to do this:

Terminal window
# This will need to be done with an Administrative PowerShell Prompt
[Environment]::SetEnvironmentVariable("NODE_TLS_REJECT_UNAUTHORIZED", "0", "Machine")
Terminal window
$env:NODE_TLS_REJECT_UNAUTHORIZED = '0'

Bot Brain#

Hubot has a brain, which is simply a place to store persist data. For example, you could write a script to have Hubot store URL’s for certain services, which you could append to via chat commands. You want these URL’s to persist after Hubot reboots, so it needs to save them to its brain.

There are many brain adapters for Hubot, for example MySQL, Redis and Azure Blob Storage. For this blog we install a file brain - which will just store the brain as a .json file on the disk.

Requirements#

You will need to a have a few things ready to get a Hubot setup with Slack:

  1. A Windows Machine with PowerShell 4.0+. For this tutorial I will be using a Windows 2012 R2 Standard machine with GUI. Once you get comfortable with Hubot you may decide to switch to using Server Core, which is a great choose for running Hubot
  2. Administrative access in your Slack group to create a Hubot integration

Create a Slack Integration for Hubot#

To have Hubot communicating with Slack, we need to configure an integration. From Slack:

Slack - Apps & Custom Integrations

Slack - Apps & Custom Integrations

After the integration has been added, you will be provided an API Token, something like xoxb-XXXXX-XXXXXX. We will need this later so note it down.

Additionally, you can customize your bots icon and add channels at this screen.

Slack - Bot name & Icon

Slack - Bot name & Icon

Installing Hubot#

Install the PoshHubot module in one of two ways:

Once installed, we are going to create a configuration file that PoshHubot will use.

Terminal window
# Import the module
Import-Module -Name PoshHubot -Force
# Create hash of configuration options
$newBot = @{
Path = "C:\PoshHubot\config.json"
BotName = 'bender'
BotPath = 'C:\myhubot'
BotAdapter = 'slack'
BotOwner = 'Matt <matt@email.com>'
BotDescription = 'my awesome bot'
LogPath = 'C:\PoshHubot\Logs'
BotDebugLog = $true
}
# Splat the hash to the CmdLet
New-PoshHubotConfiguration @newBot

Next, we need to install all the required components for Hubot, which will be handled by the Install-Hubot command.

Terminal window
# Install Hubot
Install-Hubot -ConfigPath 'C:\PoshHubot\config.json'

This will install the following:

Removing Hubot Scripts#

Hubot comes installed with some default scripts which are not required when running on Windows. We can use the Remove-HubotScript command to remove them.

Terminal window
# Be sure to provide the correct ConfigPath for your bot
Remove-HubotScript -Name 'hubot-redis-brain' -ConfigPath 'C:\PoshHubot\config.json'
Remove-HubotScript -Name 'hubot-heroku-keepalive' -ConfigPath 'C:\PoshHubot\config.json'

Installing Hubot Scripts#

There are many third party scripts available for Hubot to extend its functionality. You can search for them on the Node.js package manager site or on GitHub. We will use the Install-HubotScript function to install some useful scripts.

Terminal window
# Authentication Script, allowing you to give permissions for users to run certain scripts
Install-HubotScript -Name 'hubot-auth' -ConfigPath 'C:\PoshHubot\config.json'
# Allows reloading Hubot scripts without having to restart Hubot
Install-HubotScript -Name 'hubot-reload-scripts' -ConfigPath 'C:\PoshHubot\config.json'
# Stores the Hubot brain as a file on disk
Install-HuBotScript -Name 'hobot-brain-file' -ConfigPath 'C:\PoshHubot\config.json'

Starting Hubot#

Before we can start our bot and connect it to Slack, we have to configure the environment variables required by the scripts we are using. A good way to find out what environment variables a script is using is to look it up on GitHub. For example, the jubot-brain-file script requires FILE_BRAIN_PATH to be set.

jubot-brain-file Script

jubot-brain-file Script

Additionally, the Slack adapter for Hubot requires an environment variable to be set for the Slack API token called HUBOT_SLACK_TOKEN.

We will store both of these in the config.json file we created earlier.

Open the C:\PoshHubot\config.json file and in the EnvironmentVariables section, add the new environment variables.

The completed config.json file should look something like this:

{
"Path": "C:\\PoshHubot\\config.json",
"BotAdapter": "slack",
"BotDebugLog": {
"IsPresent": true
},
"BotDescription": "my awesome bot",
"BotPath": "C:\\myhubot",
"BotOwner": "Matt <matt@email.com>",
"LogPath": "C:\\PoshHubot\\Logs",
"BotName": "bender",
"ArgumentList": "--adapter slack",
"BotExternalScriptsPath": "C:\\myhubot\\external-scripts.json",
"PidPath": "C:\\myhubot\\bender.pid",
"EnvironmentVariables": {
"HUBOT_ADAPTER": "slack",
"HUBOT_LOG_LEVEL": "debug",
"HUBOT_SLACK_TOKEN": "xoxb-XXXXX-XXXXXX",
"FILE_BRAIN_PATH": "C:\\PoshHubot\\"
}
}

With all the configuration in place, we can start Hubot.

Terminal window
Start-Hubot -ConfigPath 'C:\PoshHubot\config.json'

Open up Slack and see your bot online! If for some reason your bot doesn’t connect, you can find the logs in the LogPath defined earlier in the config.json file.

Hubot comes with some built in commands, so you can directly message your bot with help and see if you get a response back.

Speaking to Hubot for the first time

Speaking to Hubot for the first time

If you want your bot to join certain channels, you can enter /invite @bender in Slack to bring him into the channel. To have Hubot perform commands, you need to address him in the channel. Try a @bender pug bomb me.

Yay! Pug Bombed!

Yay! Pug Bombed!

Integrating Hubot with PowerShell#

We have our Hubot joined to Slack and we have triggered a few pug bombs, but it is time to do something useful - create our own script.

The Hubot documentation covers scripting in detail and I recommend giving it a read before continuing on.

We are going to write a basic script to find the status of a Windows service on the machine hosting the Hubot. The plan is:

Install Edge.js and Edge-PS#

Edge.js and Edge-PS are Node.js packages which allow calling .NET and PowerShell (among other things) from Node.js.

To use them inside Hubot, we need to add them to the package.json file which is generated when we install Hubot for the first time. You can find package.json in the BotPath specified above, in our case it is C:\myhubot\packages.json. We will also add a version constraint. The latest version of each package can be found by searching the npm package manager.

After you have added them your package.json should look similar to this:

{
"name": "bender",
"version": "0.0.0",
"private": true,
"author": "PoshHubot <posh@hubot.com>",
"description": "PoshHubot is awesome.",
"dependencies": {
"hubot": "^2.18.0",
"hubot-diagnostics": "0.0.1",
"hubot-google-images": "^0.2.6",
"hubot-google-translate": "^0.2.0",
"hubot-help": "^0.1.3",
"hubot-heroku-keepalive": "^1.0.2",
"hubot-maps": "0.0.2",
"hubot-pugme": "^0.1.0",
"hubot-redis-brain": "0.0.3",
"hubot-rules": "^0.1.1",
"hubot-scripts": "^2.16.2",
"hubot-shipit": "^0.2.0",
"hubot-slack": "^3.4.2",
"edge": "^5.0.0",
"edge-ps": "^0.1.0-pre"
},
"engines": {
"node": "0.10.x"
}
}

Usually, after you update the package.json, you would need to run npm install to download the packages that have been added. This is handled behind the scenes for you by the Start-Hubot command.

Create the PowerShell script#

We need to design a PowerShell script that can be called from CoffeeScript, the Hubot scripting language. I recommend using the following methods when creating PowerShell scripts that will be called from Hubot:.

Keeping these methods in mind, I created a Get-ServiceHubot function to find the service status:

Get-ServiceHubot.ps1
<#
.Synopsis
Gets service status for Hubot Script.
.DESCRIPTION
Gets service status for Hubot Script.
.EXAMPLE
Get-ServiceHubot -Name dhcp
#>
function Get-ServiceHubot
{
[CmdletBinding()]
Param
(
# Name of the Service
[Parameter(Mandatory=$true)]
$Name
)
# Create a hashtable for the results
$result = @{}
# Use try/catch block
try
{
# Use ErrorAction Stop to make sure we can catch any errors
$service = Get-Service -Name $Name -ErrorAction Stop
# Create a string for sending back to slack. * and ` are used to make the output look nice in Slack. Details: http://bit.ly/MHSlackFormat
$result.output = "Service $($service.Name) (*$($service.DisplayName)*) is currently ``$($service.Status.ToString())``."
# Set a successful result
$result.success = $true
}
catch
{
# If this script fails we can assume the service did not exist
$result.output = "Service $($Name) does not exist on this server."
# Set a failed result
$result.success = $false
}
# Return the result and conver it to json
return $result | ConvertTo-Json
}

I am applying some Slack formatting in my output, including the use of asterisks around words for bold and back ticks for code blocks. You will notice there are double backticks in the code so PowerShell does not interpret them.

Here is some example output from the PowerShell when the function is run against a service that exists:

Terminal window
# Dot Source the function
. .\Get-ServiceHubot.ps1
# Get a service that exists on the system
Get-ServiceHubot -Name dhcp
{
"success": true,
"output": "Service dhcp (*DHCP Client*) is currently `Running`"
}

Here is some example output from the PowerShell when the function is run against a service that doesn’t exist on the machine:

Terminal window
# Get a service that exists on the system
Get-ServiceHubot -Name MyFakeService
{
"success": false,
"output": "Service MyFakeService does not exist on this server."
}

Save the PowerShell function into the scripts folder in the Hubot directory. In my case I will be saving it to C:\myhubot\scripts\Get-ServiceHubot.ps1

Create the Hubot script#

Now that our PowerShell function is completed, we need to wire it up to Hubot using CoffeeScript.

The goal for the CoffeeScript portion is to take a users message to the bot, work out the service name, pass it into the PowerShell script and return the result to the user.

This is the script I designed to call the PowerShell function. Be sure to read the comments so you understand how it works.

get-servicehubot.coffee
# Description:
# Gets the status of a service on the Hubot server
#
# Commands:
# hubot get service <service name> - Finds the status of a service on the Hubot server
# Require the edge module we installed
edge = require("edge")
# Build the PowerShell that will execute
executePowerShell = edge.func('ps', -> ###
# Dot source the function
. .\scripts\Get-ServiceHubot.ps1
# Edge.js passes an object to PowerShell as a variable - $inputFromJS
# This object is built in CoffeeScript on line 28 below
Get-ServiceHubot -Name $inputFromJS.serviceName
###
)
module.exports = (robot) ->
# Capture the user message using a regex capture to find the name of the service
robot.respond /get service (.*)$/i, (msg) ->
# Set the service name to a varaible
serviceName = msg.match[1]
# Build an object to send to PowerShell
psObject = {
serviceName: serviceName
}
# Build the PowerShell callback
callPowerShell = (psObject, msg) ->
executePowerShell psObject, (error,result) ->
# If there are any errors that come from the CoffeeScript command
if error
msg.send ":fire: An error was thrown in Node.js/CoffeeScript"
msg.send error
else
# Capture the PowerShell outpout and convert the JSON that the function returned into a CoffeeScript object
result = JSON.parse result[0]
# Output the results into the Hubot log file so we can see what happened - useful for troubleshooting
console.log result
# Check in our object if the command was a success (checks the JSON returned from PowerShell)
# If there is a success, prepend a check mark emoji to the output from PowerShell.
if result.success is true
# Build a string to send back to the channel and include the output (this comes from the JSON output)
msg.send ":white_check_mark: #{result.output}"
# If there is a failure, prepend a warning emoji to the output from PowerShell.
else
# Build a string to send back to the channel and include the output (this comes from the JSON output)
msg.send ":warning: #{result.output}"
# Call PowerShell function
callPowerShell psObject, msg

Save the CoffeeScript into the Hubot scripts directory as well, in my case this will be C:\myhubot\scripts\get-servicehubot.coffee.

Testing the script#

To load the script into Hubot, you need to restart the bot:

Terminal window
Restart-Hubot -ConfigPath 'C:\PoshHubot\config.json'

You will notice that npm installs the Edge.js dependencies we added in the package.json.

Automatic installation of npm dependencies

Automatic installation of npm dependencies

Check the logs in LogPath defined earlier in the config.json file to make sure that Hubot started successfully and loaded your script. You should see a line in the log file like this:

[Sun Jan 24 2016 10:25:59 GMT-0800 (Pacific Standard Time)] DEBUG Parsing help for C:\myhubot\scripts\get-servicehubot.coffee

When your bot joins the channel, ask it for help again. You will notice that the get service command has been added to the help. This is done automatically when you fill out the header part of the CoffeeScript script.

Command added to Hubot Help

Command added to Hubot Help

Now you can try some get service <service> commands and see the results:

Get service is now completed!

Get service is now completed!

Wrapping Up#

Hubot with PowerShell is a fantastic way to bring automation to your environment. With a tiny amount of CoffeeScript you can take your pre-existing PowerShell functions and make them available in a chat channel for anyone in your team to access. This is especially useful for allowing people in your company to access information on-demand from places Operations teams may only have access to.

I’d love to hear about the cool scripts you come up with when leveraging Hubot and PowerShell! Tweet me @matthodge.