Version 2

    I was given the task to create a way to monitor AWS RDS instances for our AWS accounts as this was a black hole for our monitoring.  I was able to create a PowerShell script that would connect to a specified AWS Account and pull a given metric from cloudwatch and return it to SolarWinds.

     

    Notes:

    • User with Access Key for each AWS Account that you want to pull metrics from
      • I am using the Users that were created for the Cloud Infrastructure Monitoring in Orion (the permissions seem to give me access to pull the necessary metrics)
    • AWS Powershell Tools and AWS SDK
      • I had to download the .msi for the AWS Powershell Tools, that I installed on my Primary Application Server and all APEs that I have in my WAN environment
      • I couldn't run the install-module command from any of my servers because the servers are behind a proxy and are only allowed to access a few approved white listed sites and powershell gallery is not one of them yet.
      • I couldn't create this as a Powershell script template within SAM.  I would receive an error message importing the module when it ran.  The script itself would run from the servers just fine.  I figured out that because of the cloud infrastructure monitoring in orion, that there was a .dll mismatch on my server.
      • I had copy the script to a folder location on my server and then create a VBS script monitor in SAM that passed the necessary values to the PowerShell Script which then returns the values back to the vbs script template.
    • Secret Access Key for the corresponding user access key is stored in an encrypted file on the server

    =================================================================================================================================================================

    Here is the PowerShell Script:

     

    ################## Functions #############################################

     

     

    #This function creates an eventlog source and a named windows event log, sets the event log to overwrite and sets the size

    # to 50 MB.  If the event log already exists it will just create the event log source and if both exists it does nothing.

     

    Function Create-EventSource

    {

      param(

        [string]$logfileName,

        [string]$sourceName

      )

     

      If([System.Diagnostics.EventLog]::Exists($logfileName))

        {

            #write-output "$logFileName Exists"

            If([System.Diagnostics.EventLog]::SourceExists($sourceName))

            {

            }

            else

            {

                New-EventLog -LogName $logfilename -Source $sourceName

            }

        }

        else

        {

            $event = "Creating $logfilename EventLog"

            New-EventLog -LogName $logfileName -Source $sourceName

            Limit-EventLog -LogName $logFileName -OverflowAction OverwriteAsNeeded -MaximumSize 50MB

            Write-EventLog -LogName Application -Source OVO -EntryType Information -Category 0 -EventId 200 -Message $event

        }

    }

     

     

    #converts a password from a security string to clear text

    Function Convert-Password

    {

        param(

            [security.securestring]$tmpPW

        )

        $Marshal = [System.Runtime.InteropServices.Marshal]

        $Bstr = $Marshal::SecureStringToBSTR($tmpPW)

        $Password = $Marshal::PtrToStringAuto($Bstr)

        $Marshal::ZeroFreeBSTR($Bstr)

        return $Password

    }

    ###################################### Main ########################################

     

    $eventlogName = "EventMonitoring"

    $eventLogMessage = ""

    $sourceName = "AWS-RDS-Performance"

    $Key = "d:\slw\AWSInfFile.reg" #this is a randomly generated 256 bit encryption file

     

    #variables:

    $proxyServer = "<PROXYNAME>"

    $proxyPort = "<PROXYPORT>"

     

    #the proxy that we have uses authentication so the password was encrypted to be stored on the server as opposed to plain text

    $proxyPW = Get-Content "D:\slw\proxyPW.txt" | ConvertTo-SecureString -Key (Get-Content -Path $Key)

    $proxyCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList "<PROXYUSERNAME>",$proxyPW

     

     

    $timerange = 5 #the time range for grabbing the metric

    $starttime = ([datetime]::UtcNow.AddMinutes(-$timerange))

    $endtime = [datetime]::UtcNow

    $AWSAccountName = $args[1] # This is your AWS account, this is only needed if you are storing more than on password file for secret key

    $AWSRegion = "us-east-2" #this is the region your entities are in

    $profileName = $args[5] # The stored profile on the server

    $awsNameSpace = $args[4] # This is the name space.  you can see the different from cloudwatch for RDS is is AWS/RDS for Lambda AWS/Lambda

    $period = "60"

    $metric = $args[3] # This is the different metrics that you can pull

    $Entity = $args[0] # this is the name of the RDS instance, lambda function name, This will correspond with the dimension in the aws cmdlet

    $unit = $args[6] # unit of the metric

    $AccessKey = $args[2]  #access key for your user

     

     

     

     

     

     

    #Create and Validate the Event Log Setup

     

     

    Create-EventSource -logfileName $eventlogName -sourceName $sourceName

     

     

    $eventLogMessage += "Time Range = $timerange`r`n"

    $eventLogMessage += "AccountName = $AWSAccountName`r`n"

    $eventLogMessage += "Profile = $profileName`r`n"

    $eventlogMessage += "NameSpace = $awsNameSpace`r`n"

    $eventLogMessage += "Metric = $metric`r`n"

    $eventLogMessage += "Node = $Entity`r`n`r`n"

     

     

    #importing the AWS Powershell Module

    Try

    {

        $eventLogMessage += "Importing AWS Powershell Module.."

        #Import-Module -name AWSPowerShell -ErrorAction Stop   #you can attempt to do this, but may have an issue from within SolarWinds

    Import-module 'C:\Program Files (x86)\AWS Tools\PowerShell\AWSPowerShell\AWSPowerShell.psd1' -ErrorAction Stop  #I had to specify the exact path to the module that needed to be imported.  If i didn't, it would fail to import. This could have something to do with not being able to use Install-Module in powershell.

        $eventLogMessage += ".Successfully Imported`r`n"

    }

    Catch

    {

        $eventLogMessage += ".Failed to Import`r`n"

        $eventLogMessage += "Error:  $($error[0])`r`n"

        $Status = "Bad"

        $Statistic = "-1"

        $Message = "AWSPowershell Module Failed to Import"

        Write-EventLog -LogName $eventlogName -Source $sourceName -EntryType Error -Category 0 -EventId 3245 -Message $eventLogMessage

        $status

        $statistic

        $Message

        exit

    }

     

     

    #Building the Proxy Connection that will be used - if you don't have a proxy server to go through then you wouldn't need this

    Try

    {

        $eventLogMessage += "Configuring Proxy for AWS Connection.."

        Set-AWSProxy -Hostname $proxyServer -Port $proxyPort -Credential $proxyCred -ErrorAction stop

        $eventLogMessage += ".Successfully Configured`r`n"

    }

    catch

    {

        $eventLogMessage += ".Failed to Configure`r`n"

        $eventLogMessage += "Error:  $($error[0])`r`n"

        $Status = "Bad"

        $Statistic = "-1"

        $Message = "AWSProxy Configuration Failed.  Check Password File on Server"

        Write-EventLog -LogName $eventlogName -Source $sourceName -EntryType Error -Category 0 -EventId 3245 -Message $eventLogMessage

        $status

        $statistic

        $Message

        exit

    }

     

     

    #Build The accesskey and secretkey for the account - Since I have multiple accounts, i have stored the secret keys in encrypted files corresponding to the AWS Account Name.  This takes the encrypted files and returns the secret key in clear text

     

     

    $SKey = Get-Content "D:\slw\$($AWSAccountName).txt" | ConvertTo-SecureString -Key (Get-Content -Path $Key)

    $SecretKey = Convert-Password -tmpPW $Skey

     

     

    #Building the Credential Set for AWS Connection

    Try

    {

        $eventLogMessage += "Configuring the AWS Credentials`r`n"

        $eventLogMessage += "   Setting AccessKey,SecretKey and Profile.."

        Set-AWSCredentials -AccessKey $accessKey -SecretKey $secretKey -StoreAs $ProfileName -ErrorAction Stop -WarningAction SilentlyContinue

        $eventLogMessage += ".Successfully Set`r`n"

        $eventLogMessage += "   Initializing AWS Defaults.."

        Initialize-AWSDefaults -ProfileName $ProfileName -Region $AWSRegion -ErrorAction Stop -WarningAction SilentlyContinue

        $eventLogMessage += ".Succesfully Initialized`r`n"

        $eventLogMessage += "   Storing Profile in AWS Credentials.."

        Set-AWSCredentials -ProfileName $ProfileName -ErrorAction Stop -WarningAction SilentlyContinue

        $eventLogMessage += ".Successfully Stored`r`n"

        $eventLogMessage += "Finished Configuring the AWS Credentials`r`n"

    }

    Catch

    {

        $eventLogMessage += "Failed to Completely Configure the AWS Credentials.`r`n"

        $eventLogMessage += "Error:  $($error[0])`r`n"

        $Status = "Bad"

        $Statistic = "-1"

        $Message = "Failed to Completely Configure the AWS Credentials"

        Write-EventLog -LogName $eventlogName -Source $sourceName -EntryType Error -Category 0 -EventId 3245 -Message $eventLogMessage

        $status

        $statistic

        $Message

        exit

    }

     

     

    #We have made the connection to AWS, lets pull some data

    #Using Get-CWMetricStatistic, the dimension parameter is a hash table, that needs the name and value to match was is in cloud watch, so if dimensonname was dbindentifier, the value would need to be the dbindentifier name in cloud watch.

    Try

    {

        $eventLogMessage += "Collecting $metric Metrics for the last $timerange minutes`r`n"

        $Data = Get-CWMetricStatistic -Namespace $awsNameSpace -Dimension @{Name=”$dimensionName”; Value=”$Entity”} -MetricName $metric -UtcStartTime $starttime  -UtcEndTime $endtime -Period $period -Statistics $stat -Unit $unit -ErrorAction Stop #Statistics can be max, min, average, sum

    }

    Catch

    {

        $eventLogMessage += "Error Collecting Metrics"

        $eventLogMessage += "Error:  $($error[0])`r`n"

    }

     

     

    #check to see if we got any data.  Making assumption that if we didn't get any data back that something is wrong with request. Also assuming that datapoint would be more than 0.  This is not a validate assumption when it comes to lambda functions.

    If($data.Datapoints.count -eq 0)

    {

        $eventLogMessage += "No data points were returned.  Manually Validate the Collection`r`n"

        $Status = "Bad"

        $Statistic = "-1"

        $Message = "No data points were returned. Validate Metric call"

      

    }

    else

    {

        $i=0

        $eventLogMessage += "Generating the $stat $metric for the last $timerange minutes`r`n"

        $sum = 0

        foreach($point in $data.Datapoints)

        {

            $sum += $point.average

            $i++

        }

        $AverageValue = $sum/$data.Datapoints.count

        $AvgValue = [math]::round($AverageValue,2)

        $Status = "Good"

        $Statistic = $AvgValue

        $eventLogMessage += "$($Entity): Average $($metric): $AvgValue"

    }

     

     

    If($unit -eq "Bytes")

    {

        $statistic = [math]::Round(($statistic/1mb),2)

        $unit = "MB"

    }

     

     

    If($status -eq "Good")

    {

        write-host "Statistic: $statistic"

        write-host "Message: $unit"

    }

    else

    {

        write-host "Statistic: $statistic"

        write-host "Message: $message"

    }#>

     

     

    #>

    Write-EventLog -LogName $eventlogName -Source $sourceName -EntryType Information -Category 0 -EventId 200 -Message $eventLogMessage

     

    ============================================================================================================================================

    Here is the vbs script that i am using for the template:

    The entity is the node.caption.

    'entity name

    dim accountname: accountName = "<acctName>"

    dim accesskey: accesskey = "<access key>"

    Dim metric: metric = "<metric name>"

    Dim strNamespace: strNamespace = "<namespace>"

    Dim profilename: profilename = "<profilename>"

    Dim unit: unit = "<UNIT>"

    Dim dimensionName: dimensionName = "<DimensionName>"

     

    set oShell=CreateObject("WScript.Shell")

    dim objArgs : set objArgs = wscript.arguments

    Dim strEntity: strEntity = objArgs(0)

    Dim ps: ps = "powershell.exe -ExecutionPolicy bypass -command C:\SLW\Scripts\AWSCloudWatchVBS.ps1 " & strEntity & " " & accountName & " " & accesskey & " " & metric & " " & strNamespace & " " & profilename & " " & unit  & " " & dimensionName

    Dim exec: Set exec = oShell.Exec(ps)

    exec.StdIn.Close()

    WScript.Echo exec.StdOut.ReadAll()

     

    ==============================================================================================================================================

    Here is the function to store and encrypt passwords that can be unencrypted by any user

     

    Function Create-EncryptFile

    {

        param(

            [string]$tmpPW,

            [string]$tmpName

        )

        $encryptionKeyFile = <FILEPATH TO 256BIT ENCRYPTION FILE>

        $passwordFile = "d:\slw\$($tmpName).txt" #path to password file

        ConvertTo-SecureString -String $tmpPW -AsPlainText -Force |

        ConvertFrom-SecureString -Key (Get-Content -Path $encryptionKeyFile) |

        Out-File -FilePath $passwordFile

        Write-Output "PW file created for $tmpName"

    }

     

    Create-EncryptFile -tmpName "<NAME FOR THE PW FILE>" -tmpPW "<PW TO ENCRYPT>"

     

    ========================================================================================================================================================

    Here is how you create a 256 bit encryption file

    #create 256 AES Key

    Get-Random -Count 32 -InputObject (0..255) | out-file -FilePath <FILEPATH>

     

     

    Any question about what I did, please let me know.

     

    Steve H

     

    I also have a set of scripts for pulling information from Azure Monitor, specifically Azure Managed SQL Instances.  If anyone wants that, I can do a write up on what i did for that.