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
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.
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?
Hi Amar,
Sorry for the late reply. The package I use for running Powershell script in SCOM is the “Sample Management Pack” which can be found here: https://gallery.technet.microsoft.com/Sample-Management-Pack-17b76379
Best Regards,
Casper
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
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:ScriptsWinSCP”, #Path to folder containing the winscp and test files
# $localFile = “c:ScriptsWinSCPSCOMTestFileSecureFTP.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:ScriptsWinSCPSCOMTestFileSecureFTP.txt"
# Load WinSCP .NET assembly
# Use "winscp.dll" for the releases before the latest beta version.
[Reflection.Assembly]::LoadFrom("\C:Program Files (x86)WinSCPWinSCPnet.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