SSH Module for Service Management Automation

This module is reworked from the version posted by Eamon O’Reily on the technet gallery.

https://gallery.technet.microsoft.com/scriptcenter/Sample-PowerShell-module-8d961a1c/view/Discussions#content

The original version utilised temporarily created text files and folders on the system drive for capturing putty / plink output.  This modified version removes the use of files which allows the module to work on hardened servers.  It also introduces an ExitCode switch to allow return codes to be outputted instead of stream text.

The linked zip file (which is directly imported as a Service Management Automation module) contains plink.exe (and required license agreement).     The source code is below.

TIP: When writing SMA modules, try to keep module names (i.e. the folder) to 8 characters or less.  I’ve now seen on seemingly identical machines where Service Management Automation fails to extract routines from modules if the name is larger.   

Download the ssh module for Service Management Automation here.

Source Code

[[powershell]]
#
    .SYNOPSIS
    Executes a PuTTY / PLink.exe SSH command using either a password or a key file.
	.DESCRIPTION
	This module has a dependency on PLink.exe available in the PuttyFiles folder. You can download this file from http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html 
	and place this in the PuttyFiles folder. You can then zip up the SSH folder and import this into System Center R2 - Orchestrator Service Management Automation.
    .Example
    Invoke-SSHCommand -HostName "oracle7.domain.com" -Password MySecret1 -SSHCommand "ls -l" -UserName fred -AcceptHostKey 
    Returns the text stream from the executed command
   .Parameter Hostname
    A resolveable hostname (or IP address) for a linux host
   .Parameter SSHCommand
    The SSH command to be used against the host
   .Parameter AcceptHostKey
    A switch to allow unrecognised Host Keys to be accepted  
   .Parameter ExitCode
    A switch that allows exitcodes to be returned instead of (the default) text stream 
    .Parameter Username
    The username to connect to the target machine
    .Parameter Password
    The Password to connect to the target machine         
    .Parameter KeyFilePath
    Thepath to a key file to connect to the target machine 

   .Inputs
    [string]
   .OutPuts
    [string]
    [int]
   .Notes
    NAME:  Invoke-SSHCommand
    AUTHOR: Laurie Rhodes
    LASTEDIT: 8/11/2015
    KEYWORDS:
#>
function Invoke-SSHCommand{
    [CmdletBinding(DefaultParameterSetName='UseKeyAuthentication')]
    param(
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $HostName,

        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $SSHCommand,
        
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $UserName,

        [Parameter(Mandatory=$False)]
        [Switch]
        $AcceptHostKey,

        [Parameter(Mandatory=$False)]
        [Switch]
        $ExitCode,

        [Parameter(ParameterSetName='UsePasswordAuthentication',Mandatory=$True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Password,

        [Parameter(ParameterSetName='UseKeyAuthentication', Mandatory=$True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $KeyFilePath
    )

    

    $Invocation = (Get-Variable MyInvocation -Scope 1).Value
    $CMDLetPath = Split-Path $Invocation.MyCommand.Path
   
    $PathtoPlink = "$($CMDLetPath)\PuttyFiles\plink.exe"
     
    if (!(Test-Path $pathtoPlink)) 
    { 
	    Throw  "Plink.exe is not located with module files Please download from http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html"
    }

# Workaround to support PowerShell via Odata IIS extensions on Server 2012R2
# PLink isn't able to query DNS so PowerShell converts any 
# Hostname to an IP prior to calling Plink


try{ $IPAddress = [IPAddress]$HostName
   #"valid IP"
}
catch{
  # "not a valid IP"
      #Resolve-DNSName throws an error if a DNS name is not resolved
      $old_ErrorActionPreference = $ErrorActionPreference
      $ErrorActionPreference = 'SilentlyContinue'

      $IPAddress = (Resolve-DnsName -Name $HostName).IPAddress

      $ErrorActionPreference = $old_ErrorActionPreference 

}


#Continue to use Plink with an IP Address

if ($AcceptHostKey)
	{
    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = "cmd.exe"
    $params = "/c echo y | ""$($pathtoPlink)"" $IPAddress"
    $pinfo.Arguments = $params
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $processstart = $p.Start() 
    $p.WaitForExit()
    $stdout = $p.StandardOutput.ReadToEnd()
    $stderr = $p.StandardError.ReadToEnd()

    [int]$returncode = $p.ExitCode
    write-verbose "Accept Host Key Exit Code: $returncode"
}


if ($Password)
	{
       Write-Verbose "Calling Password Authentication"
       $pinfo = New-Object System.Diagnostics.ProcessStartInfo
       $pinfo.FileName = $pathtoPlink
       $params = "-ssh -batch $($IPAddress) -l $($UserName) -pw $($Password)  $($SSHCommand)"
       $pinfo.Arguments = $params
       $pinfo.RedirectStandardError = $true
       $pinfo.RedirectStandardOutput = $true
       $pinfo.UseShellExecute = $false
       $p = New-Object System.Diagnostics.Process
       $p.StartInfo = $pinfo
       $processstart = $p.Start() 
       $p.WaitForExit()
       $stdout = $p.StandardOutput.ReadToEnd()
       $stderr = $p.StandardError.ReadToEnd()
       [int]$returncode = $p.ExitCode

       write-verbose "stdout: $stdout"
       write-verbose "stderr: $stderr"
       write-verbose "exit code: $returncode"
}
else{
    # If a key file is set then run the command using the provided key file. All Key files should be placed in the KeyFiles folder and
    # must not have a passphrase protecting them. Ensure that access to the runbook workers is restricted to protect these files.
    if (Test-Path $KeyFilePath) {
       Write-Verbose "Calling Key File Authentication"
       $pinfo = New-Object System.Diagnostics.ProcessStartInfo
       $pinfo.FileName = $pathtoPlink
       $params = "-ssh -batch $($IPAddress) -l $($UserName) -i "+[char]34 + $KeyFilePath +[char]34 +" $($SSHCommand)"

       $pinfo.Arguments = $params
       $pinfo.RedirectStandardError = $true
       $pinfo.RedirectStandardOutput = $true
       $pinfo.UseShellExecute = $false
       $p = New-Object System.Diagnostics.Process
       $p.StartInfo = $pinfo
       $processstart = $p.Start() 
       $p.WaitForExit()
       $stdout = $p.StandardOutput.ReadToEnd()
       $stderr = $p.StandardError.ReadToEnd()
       [int]$returncode = $p.ExitCode
       write-verbose "stdout: $stdout"
       write-verbose "stderr: $stderr"
       write-verbose "exit code: $exitcode"

        }
        else { Throw "Key File not found" }
	}
 
 if ($stderr.Length -ne 0){ Throw $stderr }
 
 if ($ExitCode){
   $response = $returncode
 }
 else{
   $response = $stdout
 }
 return $response
}

Export-ModuleMember Invoke-SSHCommand
[[/powershell]]

 

11/2015 - The version of module here has been modified to support being utilised via IIS Odata / REST call.  This allows an IIS server to relay SSH commands  to local linux machines from its Odata interface.  For more details, have a read of this post:

Calling PowerShell Modules with REST / ODATA API