Building a runbook with Powershell and Telegram

Building a runbook with Powershell and Telegram

In this article, we will go through building a runbook with Powershell and Telegram to allow us to interact with the script with just a message.

If you haven’t read it yet, have a look at Automating Telegram Messages with Powershell – In there, I build a simple script that helps me interact with users members of a Telegram group. That article has the core of what I’m about to show you now and will also explain how to get a Telegram Bot setup.

So in this specific example, we will build a very simple runbook with Powershell that will integrate with Telegram: this will allow us to “talk” to our powershell script with just a Telegram message. What’s cool about the below, is that you can keep adding “switches” to increase the amount of tasks the script can perform. What’s even cooler is that the script below will allow you to run powershell with a Telegram message and get the output on your phone! In other words, I can tell the script to run whatever command I want. 🙂

Here’s an example of me messaging the Telegram bot with “run get-childitem -path c:\users” and the result (screenshot taken from the web interface of Telegream, but it’s the same on your phone):

Telegram-simple-runbook-example

The script leverages a “Switch” to handle multiple IFs and specifically, it’ll will follow this flow:

  • Check for any new Message in the Telegram conversation, in a loop.
    • Note: To be safe, when the script starts up, it’ll ignore the last message sent, and will be listening for any new message right after that.
    • To perform this, I used the Message Timestamp that is in Telegram’s json ($LastMessage.message.date).
  • If a new Message has been received, check with a Switch if the message contains either “run *” and “quit_script”.
    • If it contains “run *” (example: run Get-ChildItem -Path C:\), it’ll split the string received and run the command in powershell.
    • If the message is “quit_script”, it’ll exit the script running on the computer.
    • If it doesn’t contain anything like that (example: Hello), it’ll tell the user that the script doesn’t understand that command.

Now imagine the possibilities, you may add a specific case in the Switch where you can instruct the script to perform any task, all of this from a Telegram text message on your phone.

The Script

$BotToken = "TOKEN HERE"
$ChatID = 123456789

#Time to sleep for each loop before checking if a message with the magic word was received
$LoopSleep = 3


#Get the Last Message Time at the beginning of the script:When the script is ran the first time, it will ignore any last message received!
$BotUpdates = Invoke-WebRequest -Uri "https://api.telegram.org/bot$($BotToken)/getUpdates"
$BotUpdatesResults = [array]($BotUpdates | ConvertFrom-Json).result
$LastMessageTime_Origin = $BotUpdatesResults[$BotUpdatesResults.Count-1].message.date

#Read the responses in a while cycle
$DoNotExit = 1
#$PreviousLoop_LastMessageTime is going to be updated at every cycle (if the last message date changes)
$PreviousLoop_LastMessageTime = $LastMessageTime_Origin

$SleepStartTime = [Int] (get-date -UFormat %s) #This will be used to check if the $SleepTime has passed yet before sending a new notification out
While ($DoNotExit)  {

  Sleep -Seconds $LoopSleep
  #Reset variables that might be dirty from the previous cycle
  $LastMessageText = ""
  $CommandToRun = ""
  $CommandToRun_Result = ""
  $CommandToRun_SimplifiedOutput = ""
  $Message = ""
  
  #Get the current Bot Updates and store them in an array format to make it easier
  $BotUpdates = Invoke-WebRequest -Uri "https://api.telegram.org/bot$($BotToken)/getUpdates"
  $BotUpdatesResults = [array]($BotUpdates | ConvertFrom-Json).result
  
  #Get just the last message:
  $LastMessage = $BotUpdatesResults[$BotUpdatesResults.Count-1]
  #Get the last message time
  $LastMessageTime = $LastMessage.message.date
  
  #If the $LastMessageTime is newer than $PreviousLoop_LastMessageTime, then the user has typed something!
  If ($LastMessageTime -gt $PreviousLoop_LastMessageTime)  {
    #Looks like there's a new message!
    
	#Update $PreviousLoop_LastMessageTime with the time from the latest message
	$PreviousLoop_LastMessageTime = $LastMessageTime
	#Update the LastMessageTime
	$LastMessageTime = $LastMessage.Message.Date
	#Update the $LastMessageText
	$LastMessageText = $LastMessage.Message.Text
	
	Switch -Wildcard ($LastMessageText)  {
	  "run *"  { #Important: run with a space
	    #The user wants to run a command
		$CommandToRun = ($LastMessageText -split ("run "))[1] #This will remove "run "
		$Message = "Ok $($LastMessage.Message.from.first_name), I will try to run the following command: `n<b>$($CommandToRun)</b> `nPlease bear with me and remember that the output might not be so clean.."
		$SendMessage = Invoke-RestMethod -Uri "https://api.telegram.org/bot$($BotToken)/sendMessage?chat_id=$($ChatID)&text=$($Message)&parse_mode=html"
		
		#Run the command
		Try {
		  Invoke-Expression $CommandToRun | Out-String | %  {
		    $CommandToRun_Result += "`n $($_)"
		  }
		}
		Catch  {
		  $CommandToRun_Result = $_.Exception.Message
		}

		
		$Message = "$($LastMessage.Message.from.first_name), I've ran <b>$($CommandToRun)</b> and this is the output:`n$CommandToRun_Result"
		$SendMessage = Invoke-RestMethod -Uri "https://api.telegram.org/bot$($BotToken)/sendMessage?chat_id=$($ChatID)&text=$($Message)&parse_mode=html"
	  }
	  "quit_script"  {
		#The user wants to stop the script
		write-host "The script will end in 5 seconds"
		$ExitMessage = "$($LastMessage.Message.from.first_name) has requested the script to be terminated. It will need to be started again in order to accept new messages!"
		$ExitRestResponse = Invoke-RestMethod -Uri "https://api.telegram.org/bot$($BotToken)/sendMessage?chat_id=$($ChatID)&text=$($ExitMessage)&parse_mode=html"
		Sleep -seconds 5
		$DoNotExit = 0
	  }
	  default  {
	    #The message sent is unknown
		$Message = "Sorry $($LastMessage.Message.from.first_name), but I don't understand ""$($LastMessageText)""!"
		$SendMessage = Invoke-RestMethod -Uri "https://api.telegram.org/bot$($BotToken)/sendMessage?chat_id=$($ChatID)&text=$($Message)&parse_mode=html"
	  }
	}
	
  }
}

The script has a lot of comments, so it should really be self-explanatory, there’s just one thing I want to point out: I’m using Out-String when running the command and then, based on that, I store the output in an string. Basically, for each line that is returned, I will end up with a single string which contains new-lines as well. This will make it more readable, remember that the output is being returned on a text!

Invoke-Expression $CommandToRun | Out-String | %  {
  $CommandToRun_Result += "`n $($_)"
}

 

IT Droplets

IT Droplets