FTP / SFTP monitor for SCOM

In this post we’ll make a script for a FTP / SFTP monitor that can monitor the FTP/SFTP status, by doing the following operations:
-Log in
-Upload a file
-Download a file
-Delete the file

Since Powershell doesn’t have any built-in ftp support I was looking for some alternatives, and since I use WinSCP normally for ftp/sftp I found that they also support Powershell scripting, so why not take advantage of this? This guide was written with great help from WinSCP’s own page: https://winscp.net/eng/docs/library_powershell

Get the SSH fingerprint (This part is only nessary for the SFTP solution). Since we need the SSH fingerprint for logging into the SFTP we can obtain this by connecting to the SFTP from the normal WinSCP interface and doing the following steps:

-Download WINSCP: https://winscp.net/eng/download.php

-Make a SFTP session to the server you wish to monitor and connect to this session

-When the session is open, navigate to: Session->Server/Protocol information

-SSH fingerprintet is written under Server host key fingerprint

-Copy the fingerprint and paste it in the below script where there’s a lot of xx:xx:xx:

 

Obtaining the WinSCP .NET Assembly

Now we need to download WinSCP in a Powershell friendly edition, called the WinSCP .NET Assembly, get it by following this link: https://winscp.net/eng/docs/library_install

The files you need to succesfully run the script from a SCOM server is the .exe file and the .dll file.

 

Preparing the script for SCOM

Since we want to make the script work for SCOM, we need a way to communicate back to SCOM, this can be done by creating a propertybag. But don’t worry, we can also just for test communicate to the command-line, or if you want the final script just to write the result to the commandline, or send an exitcode.

The lines we need to implement a propertybag is the following lines:

$api = New-Object -comObject "MOM.ScriptAPI"   
$PropertyBag = $api.CreatePropertyBag()

Now we want to write either a succes or a failure to the propertybag, this is optained in either finally or the catch of the exception, first the succes:

$PropertyBag.AddValue("State","Healthy")

And the failure:

$PropertyBag.AddValue("Description",$_.Exception.Message)
$PropertyBag.AddValue("State","Error")

Ok, so what is happening here? We just set the same parameters in SCOM to some specific string. We set the State to Healthy or Error, and write the exception message to the Description field.

Notice that we can just uncomment the lines if we want them written to the console/commandline on the following lines:

    Write-Host ("Upload of {0} succeeded" -f $transfer.FileName)
    Write-Host $_.Exception.Message #for testing only.
    #exit 1 #for testing only

And if we also want exit-codes:

exit 0

 

Setting the parameters

We also want to define which files to upload and which to delete, we set them in the start by the following lines:

param (
    $localPath = "c:\Scripts\WinSCP\",                           #Path to folder containing the winscp and test files
    $localFile = "c:\Scripts\WinSCP\SCOMTestFileSecureFTP.txt",         #File that is used to test writes/reads
    $remotePath = "/users/SCOMmonsvc/Upload/",                            # Path to user folder on ftp server 
    $remoteFile = "/users/SCOMmonsvc/Upload/SCOMTestFileSecureFTP.txt"           
)

Also I want to make a new file everytime, so I can just login and see when the file was created, and not depending on the test-file always being available, this is done by this line:

"Testing" | Out-File "c:\Scripts\WinSCP\SCOMTestFileSecureFTP.txt"

 

Please notice that we’re downloading everything in the remote folder, so please make a seperate folder for the test files, so you don’t download everything!

 

Changes if we want to monitor FTP

There’s only two things we need to change, the following two lines:

$sessionOptions.Protocol = [WinSCP.Protocol]::sftp   #secure ftp
$sessionOptions.SshHostKeyFingerprint = "ssh-rsa 1024 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"

And we want to change them into:

$sessionOptions.Protocol = [WinSCP.Protocol]::ftp   #ftp
#$sessionOptions.SshHostKeyFingerprint = "ssh-rsa 1024 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"

We just change the connection type to FTP instead of SFTP, and we just uncomment the SSH fingerprint.

 

Below is the full script. Now you just need to set it up as a monitor in SCOM.

 

param (
    $localPath = "c:\Scripts\WinSCP\",                           #Path to folder containing the winscp and test files
    $localFile = "c:\Scripts\WinSCP\SCOMTestFileSecureFTP.txt",         #File that is used to test writes/reads
    $remotePath = "/users/SCOMmonsvc/Upload/",                            # Path to user folder on ftp server 
    $remoteFile = "/users/SCOMmonsvc/Upload/SCOMTestFileSecureFTP.txt"           
)


#Generate propertybag
$api = New-Object -comObject "MOM.ScriptAPI"   
$PropertyBag = $api.CreatePropertyBag()   


#Generate content for our testfile
"Testing" | Out-File "c:\Scripts\WinSCP\SCOMTestFileSecureFTP.txt"

try
{
    # Load WinSCP .NET assembly
    Add-Type -Path "c:\Scripts\WinSCP\WinSCPnet.dll"
    # Setup session options
    $sessionOptions = New-Object WinSCP.SessionOptions
    $sessionOptions.Protocol = [WinSCP.Protocol]::sftp   #secure ftp
    $sessionOptions.HostName = "ftp.hostname.com"
    $sessionOptions.UserName = "user"
    $sessionOptions.Password = "password"
    $sessionOptions.SshHostKeyFingerprint = "ssh-rsa 1024 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"    #Comment out for regular ftp test. The fingerprint can be generated in the WINSCP UI.

    $session = New-Object WinSCP.Session

    ########################
    #Upload the file 
    ######################## 
    try
    {
        # Connect
        $session.Open($sessionOptions)

        # Upload files
        $transferOptions = New-Object WinSCP.TransferOptions
        $transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
        $transferOptions.PreserveTimestamp = $False
        #$transferOptions.NoPermissions = $False

        $transferResult = $session.PutFiles($localFile, $remotePath, $False, $transferOptions)

        # Throw on any error
        $transferResult.Check()

        # Print results
        foreach ($transfer in $transferResult.Transfers)
        {
            Write-Host ("Upload of {0} succeeded" -f $transfer.FileName)
        }
    }
    finally
    {

    }
    
    ########################
    #Download the file again
    ########################
    try
    {
        # Connect
        #$session.Open($sessionOptions)

        # Get list of files in the directory
        $directoryInfo = $session.ListDirectory($remotePath)

        # Select the most recent file
        $latest =
            $directoryInfo.Files |
            Where-Object { -Not $_.IsDirectory } |
            Sort-Object LastWriteTime -Descending |
            Select-Object -First 1

        # Any file at all?
        if ($latest -eq $Null)
        {
            Write-Host "No file found"
            exit 1
        }

        # Download the selected file
        $session.GetFiles($session.EscapeFileMask($remotePath + $latest.Name), $localPath).Check()
    }
    finally
    {

    }

    ########################
    #Delete the file
    ########################
    try
    {
        # Download the selected file
        $session.RemoveFiles($remoteFile).Check()  #Uncomment if the scomtestfile.txt file should be deleted again.
    }
    finally
    {
        # Disconnect, clean up
        $session.Dispose()
    }
    #exit 0 #for testing only
    $PropertyBag.AddValue("State","Healthy")  
}
catch [Exception]
{
    #Write-Host $_.Exception.Message #for testing only.
    #exit 1 #for testing only
    $PropertyBag.AddValue("Description",$_.Exception.Message)
    $PropertyBag.AddValue("State","Error")      
} 

$PropertyBag

Comments (5):

  1. Henrik says:

    Great writeup. I have myself used the sftp utility which don’t need to be installed. You can attach it to the MP as a resource. Then it will be available anywhere you need it.

  2. Amar says:

    SCOM doesn’t allow powershell scripts to run through the generic two state monitor available in the console.
    Could you please share more details on using this powershell script in SCOM monitor?

  3. ron says:

    Much easier – use Auto FTP Manager. Full SFTP support, automation, and much more in a easy to use package.
    http://www.deskshare.com/ftp-client.aspx

  4. Jared says:

    Hello,

    I changed a couple things and now the script is not working. I’m unable to see any errors logged in the Operations Manager or Windows PowerShell event logs. Can you see what I’m doing wrong? I’m also curious to know where the exception message is being derived from; maybe that’s why the alert stays open?

    #param (
    # $localPath = “c:\Scripts\WinSCP\”, #Path to folder containing the winscp and test files
    # $localFile = “c:\Scripts\WinSCP\SCOMTestFileSecureFTP.txt”, #File that is used to test writes/reads
    # $remotePath = “/HeartBeat/”, # Path to user folder on ftp server
    # $remoteFile = “/HeartBeat/SCOMTestFileSecureFTP.txt” #< edited. finish editing
    #)

    #Generate propertybag
    $api = New-Object -comObject "MOM.ScriptAPI"
    $PropertyBag = $api.CreatePropertyBag()

    #Generate content for our testfile
    #"Testing" | Out-File "c:\Scripts\WinSCP\SCOMTestFileSecureFTP.txt"

    # Load WinSCP .NET assembly
    # Use "winscp.dll" for the releases before the latest beta version.
    [Reflection.Assembly]::LoadFrom("\\C:\Program Files (x86)\WinSCP\WinSCPnet.dll") | Out-Null #This one works!

    # Session.FileTransferred event handler

    function FileTransferred
    {
    param($e)

    if ($e.Error -eq $Null)
    {
    Write-Host ("Upload of {0} succeeded" -f $e.FileName)
    }
    else
    {
    Write-Host ("Upload of {0} failed: {1}" -f $e.FileName, $e.Error)
    }

    if ($e.Chmod -ne $Null)
    {
    if ($e.Chmod.Error -eq $Null)
    {
    Write-Host ("Permisions of {0} set to {1}" -f $e.Chmod.FileName, $e.Chmod.FilePermissions)
    }
    else
    {
    Write-Host ("Setting permissions of {0} failed: {1}" -f $e.Chmod.FileName, $e.Chmod.Error)
    }

    }
    else
    {
    Write-Host ("Permissions of {0} kept with their defaults" -f $e.Destination)
    }

    if ($e.Touch -ne $Null)
    {
    if ($e.Touch.Error -eq $Null)
    {
    Write-Host ("Timestamp of {0} set to {1}" -f $e.Touch.FileName, $e.Touch.LastWriteTime)
    }
    else
    {
    Write-Host ("Setting timestamp of {0} failed: {1}" -f $e.Touch.FileName, $e.Touch.Error)
    }

    }
    else
    {
    # This should never happen during "local to remote" synchronization
    Write-Host ("Timestamp of {0} kept with its default (current time)" -f $e.Destination)
    }
    }

    # Main script

    try
    {
    $sessionOptions = New-Object WinSCP.SessionOptions -Property @{
    Protocol = [WinSCP.Protocol]::Sftp #secure ftp
    HostName = "#######"
    UserName = "#######"
    Password = "#######"
    SshHostKeyFingerprint = "#######"
    }

    $session = New-Object WinSCP.Session
    try
    {
    # Will continuously report progress of synchronization
    $session.add_FileTransferred( { FileTransferred($_) } )

    # Connect
    $session.Open($sessionOptions)

    $LocalFileName = (Get-Date).tostring("dd-MM-yyyy-hh-mm-ss")
    New-Item -itemType File -Path $localPath -Name ($LocalFileName + ".txt")

    # $stamp = Get-Date -Format "yyyyMMdd"
    # $fileName = "export_$stamp.txt"
    # $remotePath = "/HeartBeat/$fileName"
    # $localPath = "C:\Scripts\$fileName"
    $localPath = "\\#######\#######\#######"
    $remotePath = "/#######/"
    $remoteFiles = "/#######/*.txt"

    # Synchronize files
    $synchronizationResult = $session.SynchronizeDirectories(
    [WinSCP.SynchronizationMode]::Remote, $localPath, $remotePath, $False)

    # Throw on any error
    $synchronizationResult.Check()

    }
    finally
    {

    #################################################
    #Delete File older than in local directory
    #################################################
    $limit = (Get-Date).AddSeconds(-5)

    Get-ChildItem $LocalPath -Recurse | ? {-not $_.PSIsContainer -and $_.CreationTime -lt $limit} | Remove-Item
    }

    #################################################
    #Delete the file in the remote directory
    #################################################
    try
    {
    # Delete all Files
    $session.RemoveFiles($remoteFiles).Check() #Comment if the *.txt file should not be deleted for testing.
    }
    finally
    {
    # Disconnect, clean up
    $session.Dispose()
    }
    #exit 0 #for testing only
    $PropertyBag.AddValue("State","Healthy")
    }
    catch [Exception]
    {
    #Write-Host $_.Exception.Message #for testing only.
    #exit 1 #for testing only
    $PropertyBag.AddValue("Description",$_.Exception.Message)
    $PropertyBag.AddValue("State","Error")
    }

    $PropertyBag

Leave a Reply