Posting messages to Slack channel using Windows PowerShell

Download: Send-ToSlack

As previously commented I had been exploring the use of Slack and integration with other tools. One use case was to send messages to channels within Slack when certain steps in an automated workflow were triggered and to provide notification of the status upon completion.

Therefore, I created a Windows PowerShell function that would post a message to a specified slack channel using the Web API methods available, which according to Slack:

The Slack Web API allows you to build applications that interact with Slack in more complex ways than the integrations we provide out of the box.

Currently, my requirement is only to post messages and therefore the function only provides support for the chat.PostMessage method to posts a message to a public channel, private group, or IM channel.

In order to provide authentication to the API this can be achieved by a bearer token to identify a single user which uses  a generated full-access token, see ‘https://api.slack.com/web‘ for more details and to generate/retrieve your bearer token information.

If you plan to authenticate several users, it is recommended to use OAuth2 by registering your application. Currently, this method for authenticatin is not supported within the function.

The function uses the Invoke-WebRequest cmdlet to send a request to the URL ‘https://slack.com/api/chat.postMessage‘ and provide the arguments for generating the message that will be posted to the specified Slack channel. The protocol ‘https’ is specified as all methods must be called using this protocol.

The ‘chat.PostMessage’ method has mandatory requirements to specify the token, channel and text arguments. In addition, the function currently provides the ability to specify the ‘icon’ and ‘username’ arguments when posting a message.

The response will contain a JSON object, which contains a top-level boolean property which indicates success of failure. In the event of an error posting the message to the slack channel conditional logic is used to determine if the response contains an error ‘”ok”:false’ and processing the string to terminate with an error and return the machine-readable error code.

On successfully posting a message to a channel, you will receive a customised output to the console session to confirm. The success code is determined by the response containing the string ‘”ok”:true’.

Send-ToSlack -Channel "#apitest" -Text "This is a test message generated by the function Send-ToSlack" -Token "xoxp-17822671332-9811436111-15776151506-a5a9c3855"
Successfully sent the message to the slack channel #apitest.

Slack_postMessage

 

 

 

In addition, to the above the function also provides the ability to encrypt your bearer token using an encryption key and retrieve the string using the ‘System.Management.Automation.PSCredential’ class.

 

 

Advertisements

Scripting Games – December 2015

So here is my submitted entry to the December 2015 Scripting Games Puzzle hosted at PowerShell.org to which comments have been posted on the Wrap Up containing the official answer(s).

The challenges presented in this puzzle are described below and to which I detail my approach to solving each one.

Split $list into a collection of entries, as you typed them, and sort the results by length. As a bonus, see if you can sort the length without the number.

Firstly, I split the collection of entries and store the output as a variable. In order to sort the number by length the variable is retrieved and the objects are sorted by property length. For sorting the objects without the number, the retrieved variable replaces any numbers in the objects.

Turn each line into a custom object with a properties for Count and Item

For each object in the list of items an action is performed to create a custom object for a count and item property value. In order to retrieve property values, a regular expression is used to replace all numbers to retrieve the item property value and then to replace all alphabetic characters for the item property value.

Using your custom objects, what is the total number of all bird-related items?

Here I perform a keyword match which contains all the bird-related items agaisnt the custom object and the calculate the sum of the property value count to retrieve the total number.

What is the total count of all items?

As previously, I have used regular expression to remove all alphabetic characters from each item in the collection. I then join the returned values with the ‘+’ character and pass the string as the command parameter value for the Invoke-Expression cmdlet to perform the calculation.

Windows PowerShell Exit Codes, SSIS and the Environment.Exit Method

I was recently compiling a PowerShell script which would be integrated into a SSIS package, depending on the status of the script invocation this would return custom error codes to determine success or failure as part of the package. By default, I would generally use Try/Catch statements and provide and exit code. However, when invoking the script from a SSIS package this would not be returned correctly. The cause of this issue is due the powershell script being launced from the Windows Command Processor, when running natively from Windows PowerShell the exit code was returned successfully.

After a bit of research, I came across the Environment.Exit.Method which provides the functionality to terminate the process and provide the underlying operating system the custom exit code from my script.  Basically all this involved is replacing the previous method of providing an exit code with the above .NET method. For Example:

Exit("17");
[Environment]::Exit("17");

Exposing a stream around a log file for synchronous writes using Powershell

I was recently looking at streaming output to a log file synchronously in a powershell session to write numerous events for invoking several cmdlets in a script operation. In order to achieve this I was able to leverage the FileStream and StreamWriter classes as well as creating a log function to both write the event to the console and log file.

Firstly, we will start with creating the FileStream and StreamWriter objects in order to write the event to the log file, where the filename will combine an output directory and generated filename to which in this example contain the timestamp converted to the string ‘dd-MM-yyyy’.

We will specify these as follows:

$Output = "C:\Output"
$File = ([guid]::NewGuid()  + "-example-" + (Get-Date).toString('dd-MM-yyyy') + ".log")

To generate the log filename we will combine these values using the System.IO Path class.

$Path = [System.IO.Path]::Combine($Output,$File)

In order to synchronously write to the file, we need to specify how the operating system should open the file by seeking to the end of the file (FileMode Enumeration) if this exists which requires the FileAccess.Write permission and to allow for subsequent opening of the file for reading using the File (FileShare Enumeration).

$FileMode = [System.IO.FileMode]::Append
$FileAccess = [System.IO.FileAccess]::Write
$FileShare = [IO.FileShare]::Read

Now we have determined how the operating system will open the file we need to create the FileStream and StreamWriter objects and invoke the above configuration.

$FileStream = New-Object IO.FileStream($Path, $FileMode, $FileAccess, $FileShare)
$StreamWriter = New-Object System.IO.StreamWriter($FileStream)

At this point we can now write to the log file, so we will create a Function to write events to the console by invoking the Write-Host cmdlet and log file by invoking the StreamWriter.WriteLine method.

Function Log($Event)
{
   Write-Host $Event
   $StreamWriter.WriteLine($Event)
}

For Example, if I was to output an information message to notify of the current date and time as ‘SortableDateTimePattern’ format.

Log (Get-Date -Format s)

Once the script operation is complete we now want to dispose of the StreamWriter and FileStream Dispose methods to close the underlying stream and release the file.

$StreamWriter.Dispose()
$FileStream.Dispose()

Waiting on virtual machine Guest OS customization to complete

I recently discovered a blog article from Vitali Baruh on the PowerCLI QE team in regards to waiting for the guest OS customization to complete following a deployment of a virtual machine from a template.

http://blogs.vmware.com/PowerCLI/2012/08/waiting-for-os-customization-to-complete.html

As Vitali discusses this process can be difficult to determine if this has completed in the guest operating system and if the task had completed successfully.

Previously, following the power on of a deployed virtual machine from a template I have performed a Do…Until loop to process a condition until the ‘Guest.HostName’ value retrieved from the Get-View cmdlet matches the name of the virtual machine as below:

Do     
    {
    $GuestHostName = (Get-View $VM.Id).Guest.HostName
    Start-Sleep -Seconds 10
    } 
Until ($GuestHostName -eq $VM.Name)

Whilst I have received no issues from this, there is an argument agaisnt using this method as we are not retrieving the guest OS customization task status. Now, this is where Vitali has produced an excellent blog post to describe the steps of this function which is based on a number of virtual machine events generated by the vCenter server.

The script workflow, works as follows:

  • 1. For each VM we will look for the last VmStarting event, because the customization process starts after the VM has been powered on
    • 1.1. If such an event is found, change the status to “CustomizationNotStarted”
    • 1.2. If such an event is not found, change the status to “VmNotStarted”
  • 2. Start a loop
    • 2.1. For each VM with status “CustomizationNotStarted”
      • 2.1.1. Check for posted CustomizationStartedEvent after the last power-on operation
        • 2.1.1.1. If such an event is found, change the status to “CustomizationStarted”
    • 2.2. For each VM with status “CustomizationStarted”
      • 2.2.1. Check for Succeded or Failed Event
        • 2.2.1.1. If such an event is found, change the status to the corresponding “CustomizationSucceeded” or“CustomizationFailed” values
    • 2.3. Check is there more VMs with status “CustomizationNotStarted” or “CustomizationStarted” (we should continue to monitor these VMs)
      • 2.3.1. If no, break the loop
    • 2.4. Check whether the specified timeout has elapsed
      • 2.4.1. If yes, break the loop
  • 3. Return result

For those who are provisioning virtual machines to include a guest OS customization with PowerCLI, I would definitely recommend a read of this article.

 

Modifying guest network interface with the Invoke-VMScript cmdlet


images

Set-VMGuestNetworkInterface

With the news that the ‘Set-VMGuestNetworkInterface’ cmdlet is to be deprecated in the next release of PowerCLI (http://blogs.vmware.com/PowerCLI/2014/11/announcement-future-cmdlet-deprecation.html), the question is how do I now modify the guest network interface?

The ability to perform this action can now be moved to the Invoke-VMScript cmdlet, which is a pretty cool way to invoke a script in the guest operating system of the virtual machine when VMware Tools is installed.

As per the description of the cmdlet you will need to satisfy the below:

To run Invoke-VMScript, the user must have read access to the folder containing the virtual machine and a VirtualMachine.Interaction.Console Interaction privilege. The virtual machines must be powered on and have VMware Tools installed. Network connectivity to the ESX system hosting the virtual machine on port 902 must be present to authenticate with the host or the guest OS, one of the HostUser/HostPassword (GuestUser/GuestPassword) pair and HostCredential (GuestCredential) parameters must be provided. The guest account you use to authenticate with the guest operating system must have administrator’s privileges.

To run this cmdlet against vCenter Server/ESX/ESXi versions earlier than 5.0, you need to meet the following requirements:

*You must run the cmdlet on the 32-bit version of Windows PowerShell.
*You must have access to the ESX that hosts the virtual machine over TCP port 902.
*For vCenter Server/ESX/ESXi versions earlier than 4.1, you need VirtualMachine.Interact.ConsoleInteract privilege.  For vCenterServer/ESX/ESXi 4.1 and later, you need VirtualMachine.Interact.GuestControl privilege.

As mentioned VMware Tools is required to be installed and the virtual machine to be powered on, in my use case modifying the guest network interface is a one of a number of script blocks in deployment script to customise a virtual machine and therefore in order to confirm that VMware Tools is running following a virtual machine power on event I run a statement in a script block until the Guest.ToolsStatus is retrieved as ‘toolsOK’ from the Get-View cmdlet, where the MoRef value for the virtual machine is retrieved from an associated array.

Do     
    {
    Start-Sleep -Seconds 10
    $GuestToolsStatus = (Get-View $VM.Id -Property Guest).Guest.ToolsStatus
    } 
Until ($GuestToolsStatus -eq toolsOk)

Now we want to modify the guest network interface, with the following requirements.

  • Configure IPv4 address
  • Configure subnet mask
  • Configure default gateway
  • Configure DNS servers

So, in order to modify the guest network interface I will be replacing the soon to be deprecated ‘Set-VMGuestNetworkInterface’ cmdlet with the ‘New-NetIPAddress’ and ‘Set-DnsClientServerAddress’ cmdlets in Windows Powershell.

In order to run the script inside the guest operating system and in this example a Microsoft Windows Server 2012 operating, I will create a ScriptText variable containing the syntax of the two cmdlets I wish to invoke.  Please not the ‘;’ separator to invoke multiple lines of commands in the text of the script.

$ScriptText = "New-NetIPAddress –InterfaceAlias Ethernet0 –IPAddress 10.0.0.5 -AddressFamily IPv4 –PrefixLength 24 -DefaultGateway 10.0.0.254;
Set-DnsClientServerAddress -InterfaceAlias Ethernet0 -ServerAddresses 10.0.0.1,10.0.0.2"

In the above example, we are modifying the network interface ‘Ethernet0’ to use the IPv4 address 10.0.0.5/24 with a default gateway 10.0.0.254 and the DNS servers 10.0.0.1 and 10.0.0.2.

Now, let’s actually run the command in the guest operating operating system, using the Invoke-VMScript cmdlet, where guest credentials are retrieved from paramaters supplied for my script.

Invoke-VMScript -VM $VM -ScriptText $ScriptText -GuestUser $GuestUser -GuestPassword $GuestPassword

As you can see this is a powerful way to invoke scripts inside the guest operating system. In the default state the script type invoked on a Microsoft Windows operating system is Powershell. However, you may specify the type to be Bat or for a Linux operating system Bash.

The script above, can be downloaded from https://github.com/dean1609/scripts/commit/1bd96807a899a3f8861ca676354eb129545fa1c9.

PowerCLI: Adding multiple disks to a virtual machine using the New-HardDisk cmdlet

I was recently adding a number of  hard disks to a virtual machine on an number of SCSI controller devices, so rather than using the vSphere Web Client I looked at using the New-HardDisk cmdlet in order to repeat this task of adding a new virtual machine hard disk .

I wanted to create the virtual machine hard disks on a single datastore with the below characteristics:

  • Capacity: 30GB
  • Storage Format: EagerZeroedThick
  • SCSI Controller: SCSI Controller 1

I could achieve this for a single hard disk by invoking the New-HardDisk cmdlet as below:

New-HardDisk -VM VM1 -CapacityGB 30 -Datastore "Datastore1" -StorageFormat EagerZeroedThick -Controller "SCSI Controller 1"

However, as I was required to this multiple times, I leveraged the use of pure math with the ForEach loop to achieve this task to create the six virtual hard disks.

ForEach ($HardDisk in (1..6))
    { 
    New-HardDisk -VM VM1 -CapacityGB 30 -Datastore Datastore1 -StorageFormat EagerZeroedThick -Controller "SCSI Controller 1"
    } 

Furthermore, I was adding additional hard disks on a number of SCSI controllers, I could expand the above further to invoke multiple ForEach loops for each SCSI controller, specifying a different datastore and size for each.

ForEach ($HardDisk in (1..6))
    { 
    New-HardDisk -VM VM1 -CapacityGB 30 -Datastore Datastore1 -StorageFormat EagerZeroedThick -Controller "SCSI Controller 1"
    } 
ForEach ($HardDisk in (1..2))
    { 
    New-HardDisk -VM VM1 -CapacityGB 10 -Datastore Datastore2 -StorageFormat EagerZeroedThick -Controller "SCSI Controller 2"
    } 
ForEach ($HardDisk in (1..3))
    { 
    New-HardDisk -VM VM1 -CapacityGB 20 -Datastore Datastore3 -StorageFormat EagerZeroedThick -Controller "SCSI Controller 3"
    }