Showing posts with label oms. Show all posts
Showing posts with label oms. Show all posts

Thursday, March 1, 2018

[Powershell + OMS HTTP Data Collection API] - Get & Send data from a MS-SQLServer to OMS Log Analytics

Earlier in my post about using the OMS Data Collector API, i promissed that i would post something about it but in a Powershell way of doing it - today is the day.
Somewhere in the past i had the need to put some data stored in a on-prem database to OMS so we could use it's own Analysis engine.
So, obviously, from where you run this, you need to have internet connection or configured HTTP proxy - just sayin'! :)

In my case i needed to have a timestamp stored for the last time i queried data so i don't have duplicated data in OMS, so analysis could be more accurate - this Powershell script version doesn't contain that logical part of code, it's more raw so you could understand how it's done and after it, you could obvisouly change it's logic as i did, or add some other logic.

Before we go to the script itself, let me just explain the part of how we store data and then convert it to JSON.

As you might see i store data from MSSQL in a 'System.Data.DataTable' (variable $table).
So the right to way to change it to JSON is making - $table | select $table.Columns.columnname | ConvertTo-Json
As so, you'll have data in the above and correct format :

{
"property1": "value1",
"property2": "value2"
"property3": "value3",
"property4": "value4"
}

Where "property1", "property2" and so on is your table column names. This ways you only have to concern about the query fields you want/like.

So, this is the powershell i came up with - it's been great to have something like this to do some magic.


# Database
$SQLServer = "YOUR_MSSQL_SERVER"
$SQLDBName = "YOUR_DATABASE"
$SqlQuery = "YOUR_QUERY"

#OMS
$customerId = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
$SharedKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
$dataType = "YOUR_OMS_CUSTOM_LOG_FILE_NAME"
# Function to create the authorization signature
Function New-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource)
{
  $xHeaders = 'x-ms-date:' + $date
  $stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource
  $bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash)
  $keyBytes = [Convert]::FromBase64String($sharedKey)
  $sha256 = New-Object -TypeName System.Security.Cryptography.HMACSHA256
  $sha256.Key = $keyBytes
  $calculatedHash = $sha256.ComputeHash($bytesToHash)
  $encodedHash = [Convert]::ToBase64String($calculatedHash)
  $authorization = 'SharedKey {0}:{1}' -f $customerId, $encodedHash
  return $authorization
}
#Send data to OMS
Function Send-OMSData($customerId, $sharedKey, $body, $logType)
{
 $method = 'POST'
  $contentType = 'application/json'
  $resource = '/api/logs'
  $rfc1123date = [DateTime]::UtcNow.ToString('r')
  $contentLength = $body.Length
  $signature = New-Signature `
  -customerId $customerId `
  -sharedKey $sharedKey `
  -date $rfc1123date `
  -contentLength $contentLength `
  -fileName $fileName `
  -method $method `
  -contentType $contentType `
  -resource $resource
  $uri = 'https://' + $customerId + '.ods.opinsights.azure.com' + $resource + '?api-version=2016-04-01'
  $headers = @{
    'Authorization'      = $signature
    'Log-Type'           = $logType
    'x-ms-date'          = $rfc1123date
  }

  $response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $contentType -Headers $headers -Body $body -UseBasicParsing
  return $response.StatusCode
}

Try {
    $connString = "Data Source=$SQLServer;Initial Catalog=$SQLDBName;user=YOUR_SQL_SERVER_USERNAME;password=YOUR_SQL_SERVER_PASSWORD" 
    $connection = New-Object System.Data.SqlClient.SqlConnection($connString)
    $connection.Open()
    $sqlcmd = $connection.CreateCommand()
    $sqlcmd.CommandText = $SqlQuery
    $results = $sqlcmd.ExecuteReader()
    $table = new-object “System.Data.DataTable”
    $table.Load($results)
    $connection.Close()
}

Catch [Exception] {
Write-Host $_.exception.message
    exit
}
$payload = $table | select $table.Columns.columnname | ConvertTo-Json
#send Data to OMS
Send-OMSData -customerId $customerId -sharedKey $sharedKey -body ([System.Text.Encoding]::UTF8.GetBytes($payload)) -logType $dataType


Hope you all enjoy and give it some use.

Cheers,

[Check_MK + OMS Log Analytics] - Using Log Analytics Data Collector API

So, it's been a while since i wrote something here.
Not that i've lots of visualizations, but as i said before, i use this not only as a knowlodge share 'platform' and an hobbie, but also as my notebook :)

I've been self challanged to send or integrate if you like, some Check_MK alerts with OMS Log Analytics.

So, i've decided to give it a shot.

The scenario was to send all Check_MK alerts to OMS Log Analytics and later make some analysis about that, and other data that i'm already sending there (i'll make a post about it later - something about Powershell + MSSQL data -> Azure) - Perhaps tomorow i'll post it here.

So - Check_MK has a notifications system almost like Operations Manager.
It's very flexible about what you want to notificate and whom.

You might want to read it a little more here:

http://mathias-kettner.com/checkmk_flexible_notifications.html

After googling a bit, a came across with an article about posting data using the HTTP Data Collector API, where it has a bunch of examples about how to post data to OMS - check it here :

https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-data-collector-api

Resuming a little bit, basically your input data needs to be like this :

{
"property1": "value1",
" property 2": "value2"
" property 3": "value3",
" property 4": "value4"
}

So, after knowing this, this is the Check_MK notification script i came up with (Python):



#!/usr/bin/env python
import os, sys, time, subprocess, json, requests, datetime, hashlib, hmac, base64

# HTTP/HTTPS Proxies - If you need some ... #

os.environ["HTTP_PROXY"] = 'http://:/'
os.environ["HTTPS_PROXY"] = 'https://:/'

# OMS Related Variables #
customer_id = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
shared_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
log_type = 'Your_Custom_Log_Name'

# OMS Related Functions #

# Build the API signature
def build_signature(customer_id, shared_key, date, content_length, method, content_type, resource):
    x_headers = 'x-ms-date:' + date
    string_to_hash = method + "\n" + str(content_length) + "\n" + content_type + "\n" + x_headers + "\n" + resource
    bytes_to_hash = bytes(string_to_hash).encode('utf-8')
    decoded_key = base64.b64decode(shared_key)
    encoded_hash = base64.b64encode(hmac.new(decoded_key, bytes_to_hash, digestmod=hashlib.sha256).digest())
    authorization = "SharedKey {}:{}".format(customer_id,encoded_hash)
    return authorization

# Build and send a request to the POST API
def post_data(customer_id, shared_key, body, log_type):
    method = 'POST'
    content_type = 'application/json'
    resource = '/api/logs'
    rfc1123date = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
    content_length = len(body)
    signature = build_signature(customer_id, shared_key, rfc1123date, content_length, method, content_type, resource)
    uri = 'https://' + customer_id + '.ods.opinsights.azure.com' + resource + '?api-version=2016-04-01'

    headers = {
        'content-type': content_type,
        'Authorization': signature,
        'Log-Type': log_type,
        'x-ms-date': rfc1123date
    }

    response = requests.post(uri,data=body, headers=headers)
    if (response.status_code >= 200 and response.status_code <= 299):
        print 'Accepted'
    else:
        print "Response code: {}".format(response.status_code)

# MK Alert Handling #

MK_HOSTNAME = os.environ['NOTIFY_HOSTNAME']
MK_DESCRIPTION = os.environ['NOTIFY_SERVICEOUTPUT']
MK_PLUGIN_OUTPUT = os.environ['NOTIFY_SERVICEDESC']
MK_LAST_STATE_CHANGE = time.strftime("%Y-%m-%d %H:%M:%S")
MK_SERVICESTATE = os.environ['NOTIFY_SERVICESTATE']

MK_HOST_IP = NEW_INFO.split(",")[0]
MK_DATACENTER = NEW_INFO.split(",")[1]
MK_TEAM = NEW_INFO.split(",")[2]
MK_VENDOR = NEW_INFO.split(",")[3].replace('\n','')

MK_JSON_OUTPUT = json.dumps([{'mk_hostname': MK_HOSTNAME, 'mk_description': MK_DESCRIPTION, 'mk_plugin_output': MK_PLUGIN_OUTPUT, 'mk_last_state_chage': MK_LAST_STATE_CHANGE, 'mk_servicestate': MK_SERVICESTATE }])

post_data(customer_id, shared_key, MK_JSON_OUTPUT, log_type)

After yout get this as an active notification method on your Check_MK site, you'll start to see data getting in OMS like :



This is really nice to have - the OMS analytics engine is awesome and gives you a lot of possibilities.

Obiously, you might want to use the above example on anything you want to, so, your imagination could simply adapt this to your own case scenario.

Hope you all like this thread, and i promise to post things here more frequently.

Cheers,

Thursday, August 25, 2016

SCCM (ConfigMgr) ADR Maintenance Mode in SCOM (Powershell and SCOrch)

One thing we all miss in SCCM, is the fact of the option "Disable Operations Manager alerts while software updates run" doesn't really disable all the alarmistic for a OpsMgr agent, specially if reboot is needed, and of course it'll cause alarms on the agent being updated.

After some googling i didn't find any solution to put ADR Collection Members into Maintenance in OpsMgr, i started to code some powershell.

If you might remember my later post about "OpsMgr (SCOM) - Schedule Maintenance Mode" i used the same idea to put collection members into maintenance mode.

So before the powershell script that gives me all the ADR Collection Members i had to made some changes into my OpsMgr_MM database.
I've added a new collumn "ADR_ID".

To add it just run the following SQL query :

 ALTER TABLE Scheduling  
 ADD ADR_ID varchar(100);  

Also changed the runbook powershell script that puts the agents into maintenance to this :

 try{  
   [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.EnterpriseManagement.OperationsManager.Common") | Out-Null  
   [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.EnterpriseManagement.Core') | Out-Null  
   [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.EnterpriseManagement.OperationsManager') | Out-Null  
   [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.EnterpriseManagement.Runtime') | Out-Null  
 } Catch { "" }  
 $script:SQLServer = "" # Your SQL Server  
 $script:SQLDBName = "OpsMgr_MM"  # Your Database
 $script:connString = "Data Source=$SQLServer;Initial Catalog=$SQLDBName;Integrated Security = True"  
 $script:connection = New-Object System.Data.SqlClient.SqlConnection($connString)  
 $script:Reason = [Microsoft.EnterpriseManagement.Monitoring.MaintenanceModeReason]::PlannedOther  
 $script:Transversal = [Microsoft.EnterpriseManagement.Common.TraversalDepth]::Recursive  
 $Script:log = "--------"  
 $script:class = ""  
 $script:ClassInstance = ""  
 $script:table = ""  
 # Connection to OpsMgr Management Group  
 try{  
   $MGConnSetting = New-Object Microsoft.EnterpriseManagement.ManagementGroupConnectionSettings('Your_SCOM_SERVER')  
   $MG = New-Object Microsoft.EnterpriseManagement.ManagementGroup($MGConnSetting)  
 } Catch { ' ' }  
 Function SqlDataManagement {
    param($QueryType)
    Try { 
        $connection.Open()
    } Catch {
     $Script:log += "Cannot Open DB Connection"
     exit
    } # Open Database Connection 
    $sqlcmd = $connection.CreateCommand()
    If ($QueryType -eq 'update') {
        $SqlQuery = "UPDATE Scheduling SET Status = 'Processed' WHERE ID = $ID"
        $sqlcmd.CommandText = $SqlQuery
        $results = $sqlcmd.ExecuteNonQuery()
    } # Update Database
    If ($QueryType -eq 'update_not_found') {
        $SqlQuery = "UPDATE Scheduling SET Status = 'CI NOT FOUND' WHERE ID = $ID"
        $sqlcmd.CommandText = $SqlQuery
        $results = $sqlcmd.ExecuteNonQuery()
    } # Update Database
    If ($QueryType -eq 'select') {
        $SqlQuery = "SELECT * FROM Scheduling WHERE Status = 'Not Processed' AND DATEDIFF(MINUTE,GETDATE(),[StartTime]) BETWEEN -1 AND 0 AND DATEDIFF(MINUTE,[StartTime],[EndTime]) > 5"
        $sqlcmd.CommandText = $SqlQuery
        $results = $sqlcmd.ExecuteReader()
        $script:table = new-object “System.Data.DataTable”
        $script:table.Load($results)
    } # Select rows to manage
    $connection.Close()
}

Function Get-SCOMObjectbyClass([string]$ClassDisplayName,[string]$CI) {
    $script:ClassCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringClassCriteria("Name = '$ClassDisplayName'")
    $script:MonitoringClass = $MG.GetMonitoringClasses($ClassCriteria)
    $script:MOCriteria = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringObjectGenericCriteria("DisplayName LIKE '$CI%'")
    Try {
        $script:ClassInstance = ($MG.GetMonitoringObjects($MOCriteria, $MonitoringClass[0]))[0]
    } Catch { 
        $Script:log += "$CI not found or not belonging to $ClassDisplayName"
      }
}

Function Send-Email([string]$Status) { #Mail & HTML Stuff
    $Head = ""
    $Image = "C:\OpsMgr\MM\images\logo_detail.png"
    $att1 = new-object Net.Mail.Attachment($Image)
    $att1.ContentType.MediaType = “image/png”
    $att1.ContentId = “Attachment”
    $att1.ContentDisposition.Inline = $true
    $att1.ContentDisposition.DispositionType = “Inline”
    $body = "<img src='cid:Attachment' height='12%' width='12%'/><br/>"  
    $body += "<center><h5 style=color:#999999>SCOM - Schedule Maintenance Mode</center></h5>"
    If ( $Status -eq "OK" ) {
        $body += "CI       - $CI 
" $body += "Inicio - $StartTime
" $body += "Fim - $EndTime
" $body += "Razão - $Comment
" } ElseIf ( $Status -eq "NOT OK" ) { $body += "CI - $CI
" $body += "" $body += "Putting $CI in MM failed" $body += "Reason:
" $body += "" $body += "$LOG" } $smtpServer = "SMTP.SERVER" $smtpFrom = "FROM@ADDRESS.COM" $smtpTo = "TO@ADDRESS.COM" $messageSubject = "SCOM-ScheduleMaintenanceMode - $CI" $message = New-Object System.Net.Mail.MailMessage $smtpfrom, $smtpto $message.Subject = $messageSubject $message.IsBodyHTML = $true $message.Attachments.Add($att1) $message.Body = ConvertTo-Html -Body $body -Head $head $smtp = New-Object Net.Mail.SmtpClient($smtpServer) $smtp.Send($message) } SqlDataManagement -QueryType select foreach ( $i in $table ) { $script:StartTime = (Get-Date -date ($i.StartTime).ToString()).ToUniversalTime() $script:EndTime = ($StartTime.AddMinutes(($i.EndTime - $StartTime).TotalMinutes)).ToUniversalTime() $script:Comment = $i.Comment $script:ID = $i.ID $script:Team = $i.Team $script:Type = $i.Type $script:CI = $i.CI switch ( $Type ) { "NetworkDevice" { $script:Class = 'System.NetworkManagement.Node' } "Computer" { $script:Class = 'System.Computer' } } # Switch to check which object class type it is | Add many as you may like or need. Get-SCOMObjectbyClass -ClassDisplayName "$script:Class" -CI $script:CI If ( $ClassInstance -ne $null -and ($ClassInstance.InMaintenanceMode) -ne $true ) { try { $ClassInstance.ScheduleMaintenanceMode($StartTime,$EndTime,$Reason,$Comment,$Transversal) $ClassInstance = ($MG.GetMonitoringObjects($MOCriteria, $MonitoringClass[0]))[0] If ( $ClassInstance.InMaintenanceMode -eq $true ) { SqlDataManagement -QueryType update Send-Email -Status "OK" } Else { $Script:log += = "Failed to put $CI in MM." Send-Email -Status "NOT OK" } } # Object in Maintenance Mode Catch { $Script:log += "Exception while putting $CI in MM :" + "$_.Exception.Message" Send-Email -Status "NOT OK" } } Else { $Script:log += "ClassInstance ($ClassInstance) Not Found or already in Maintenance" SqlDataManagement -QueryType update_not_found Send-Email -Status "NOT OK" } } $SCOrchLog = $script:log

The changes are :
- Function "SqlDataManagement" now accepts 'update_not_found' parameter for not found agents in OpsMgr
- Function "Get-SCOMObjectbyClass" now makes the criteria LIKE instead of = (SCCM agents are listed as HOSTNAME instead of the FQDN)
- If the agent is not found it also let you know sending you an e-mail.

So, since we've got it all done before running our new PS1 script, the script it self :

 Import-Module "D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1"  
 cd SITE_CODE:  
 $logFile = 'Your Path to SCCM_MM.log'  
 $MaintenanceWindowMode = 'Collection'  
 $SQLServer = "" #Your Server    
 $SQLDBName = "OpsMgr_MM" #YourDBName   
 $LimitDate = Get-Date  
 $AutoDeployRules = @()  
 Foreach ( $ADR in (Get-CMAutoDeploymentRule -Fast )) {   
   $ADRSchedule = (Convert-CMSchedule ($ADR.Schedule) | Select StartTime).StartTime  
   If ( $ADRSchedule -ge $LimitDate -and $ADR.LastRunTime -le $LimitDate) {  
     $AutoDeployRules += $ADR  
   }  
   Else { $ADR.Name + ' not reliable to Maintenance - Maintenance Window is in the past!' >> $logFile }  
 }  
 Foreach ( $ValidADR in $AutoDeployRules ) {  
   #ADR Info ----> SELECT NAME, Schedule  
   $ADRID = ($ValidADR.UniqueIdentifier).Guid  
   $ADRMaintenanceStart = (Convert-CMSchedule $ValidADR.Schedule).StartTime  
   $ADRName = $ValidADR.Name  
   # Check if ADR is already in Maintenance Mode Database #  
   $connString = "Data Source=$SQLServer;Initial Catalog=$SQLDBName;Integrated Security = True"   
   $connection = New-Object System.Data.SqlClient.SqlConnection($connString)    
   $connection.Open()   
   $sqlcmd = $connection.CreateCommand()    
   $SqlQuery = "set dateformat dmy ; SELECT * FROM Scheduling WHERE ADR_ID = '$ADRID' AND [StartTime] != '$(Get-Date $ADRMaintenanceStart -Format g)' ;"   
   $sqlcmd.CommandText = $SqlQuery    
   $result = $sqlcmd.ExecuteReader()  
   If ( $result.HasRows -eq $False ) {   
     If ( $MaintenanceWindowMode = "ADR" ) {  
       # <Duration>1</Duration><DurationUnits>Hours</DurationUnits>  
       [xml]$ADRDeploymentTemplate = $ValidADR.DeploymentTemplate  
       [Int32]$ADRDuration = $ADRDeploymentTemplate.DeploymentCreationActionXML.Duration  
       $ADRDurationUnits = $ADRDeploymentTemplate.DeploymentCreationActionXML.DurationUnits  
       # ADR Stop Maintenance Calculation  
       Switch ($ADRDurationUnits){  
         Hours { $ADRMaintenanceStop = (Get-Date $ADRMaintenanceStart).AddHours($ADRDuration) ; break }  
         Days { $ADRMaintenanceStop = (Get-Date $ADRMaintenanceStart).AddDays($ADRDuration) ; break}  
         Weeks { $ADRMaintenanceStop = (Get-Date $ADRMaintenanceStart).AddDays( $ADRDuration * 7 ) ; break}  
         Months { $ADRMaintenanceStop = (Get-Date $ADRMaintenanceStart).AddMonths($ADRDuration) ; break}  
        }  
     }  
     If ( $MaintenanceWindowMode = "Collection" ) {  
      $ADRMaintenanceStop = (Get-Date $ADRMaintenanceStart).AddMinutes(((Get-CMCollectionSetting -CollectionId $ValidADR.CollectionID | select -ExpandProperty ServiceWindows | select Duration).Duration))  
     }  
     # ADR Collection Members Info  
     $ADRCollectionName = (Get-CMCollection -Id $ValidADR.CollectionID).Name  
     $ADRCollectionMembers = Get-CMCollectionMember -CollectionId $ValidADR.CollectionID  
     Foreach ( $CMDeviceMember in $ADRCollectionMembers ) {  
       $CMDevice = $CMDeviceMember.Name   
       $connString = "Data Source=$SQLServer;Initial Catalog=$SQLDBName;Integrated Security = True"   
       $connection = New-Object System.Data.SqlClient.SqlConnection($connString)    
       $connection.Open()   
       $sqlcmd = $connection.CreateCommand()    
       $SqlQuery = "set dateformat dmy ; INSERT INTO Scheduling (ci,type,Team,StartTime,EndTime,Comment,Status,ADR_ID) VALUES ( '$CMDevice', 'Computer', 'SCCM', '$(Get-Date $ADRMaintenanceStart -Format g)', '$(Get-Date $ADRMaintenanceStop -Format g)', 'SCCM MaintenanceMode for : $ADRCollectionName | $ADRName' , 'NOT PROCESSED', '$ADRID');"   
       $sqlcmd.CommandText = $SqlQuery    
       $result = $sqlcmd.ExecuteNonQuery()  
     }  
   } Else { $ADRName + ' already in MM Database' >> $logFile }  
 }  

Now, you just need to create a scheduled task in your SCCM server to run whenever you might like, and ... :

# The Scheduled Task :



# OpsMgr Console Maintenance Mode window :



And that's it!

If you bump into some error or bug, please let me know, this is just too fresh and made just some few tests.

Cheers,

Friday, May 27, 2016

OpsMgr (SCOM) - Unix/Linux Agent Powershell DSC

Who doesn't have any Unix/Linux agent deployment issue ?

Remebering this earlier post i made about "OpsMgr (SCOM) - Unix/Linux Agents Requisites and Troubleshooting"
I came up with the idea to make a script that made this validations for ourselves.

Basically it'll login your unix/linux servers with your own credentials and will make a bunch of configurations tests.
But, please remember that this is my own scenario oriented, so, read and edit the code for your own scenario.

So, in first place, you'll need this library :
http://www.powershelladmin.com/wiki/SSH_from_PowerShell_using_the_SSH.NET_library

You can put it on your Modules favourite folder (eg. C:\Program Files\WindowsPowerShell\Modules)

Finally!
You can execute this script from your Unix/Linux Resource Pool gateway or MS:

 $ServerList = 'C:\Powershell\SCXAgentDSC\list.txt'  
 $SCXAgents = Get-Content -Path $ServerList  
   
 # Change values for your own  
 $user = 'Your_Run_AsAccountGoesHere!'   
 $pass = ConvertTo-SecureString 'YourPassword' -AsPlainText -Force   
 $creds = New-Object System.Management.Automation.PsCredential($user,$pass)  
   
 try { Import-Module SSH-Sessions }  
 Catch { 'No SSH Modules Found' ; Exit }  
 foreach ( $scxagent in $SCXAgents ) {  
   $scxdomain = ($scxagent -split "\.")[-2..-1] -join '.'  
     # Change values for your own here as well   
   if( (New-SshSession -ComputerName $scxagent -Username Your_Run_AsAccountGoesHere -Password "YourPassword") -notmatch “successfully”) {  
     $scxagent + ' Could not SSH (bad user / password ? | Or no route ? )'  
     $SSHStatus = "1"  
   } Else { $SSHStatus = "0" }  
   If ($SSHStatus -eq "0" ) {  
     Invoke-SshCommand -Quiet -ComputerName $scxagent -Command "sudo -l" | Out-File C:\Powershell\sudo.txt  
     Invoke-SshCommand -Quiet -ComputerName $scxagent -Command "cat /etc/issue" | Out-File C:\Powershell\issue.txt  
     Invoke-SshCommand -Quiet -ComputerName $scxagent -Command "openssl x509 -noout -in /etc/opt/microsoft/scx/ssl/scx.pem -subject -issuer -dates" | Out-File C:\Powershell\certconfig.txt  
         # This is only applied if you have limited sudo configuration   
         # This line will check if the sudo config escapes the EC (error code) variable   
     $ECCount = (Get-Content C:\Powershell\sudo.txt | select-string -SimpleMatch "EC\=0" | measure).Count  
         # This will check if you have enought perms for RPM install and uninstall  
     $RPMLines = Get-Content C:\Powershell\sudo.txt | select-string -SimpleMatch "--force /tmp/scx-monuser/scx"  
         # This will check if you have root permissions (and no further sudo config is needed - so comment the lines that does not match your scenario)  
         $SUDOALL = Get-Content C:\Powershell\sudo.txt | select-string -SimpleMatch "(root) NOPASSWD: ALL"  
         # This will check if you can re-generate certificates if needed  
     $SSLConfig = Get-Content C:\Powershell\sudo.txt | select-string -SimpleMatch "/opt/microsoft/scx/bin/tools/scxsslconfig"  
         # This will check if you have a certificate and for the correct FQDN  
     $CertConfig = Get-Content C:\Powershell\certconfig.txt | select-string -SimpleMatch "$scxagent"  
     $SCXSSLDomain = ((Get-Content C:\Powershell\certconfig.txt | Select-String -SimpleMatch "subject") -split "=")[-1]  
         # Port testing (22 and 1270)  
     Try { If ((new-object System.Net.Sockets.TcpClient("$scxagent","1270")).connected -eq $true ) { $AgentPortStatus = "OK" } Else { $AgentPortStatus = "NOT OK" } } Catch { $AgentPortStatus = "NOT OK"}  
     Try { If ((new-object System.Net.Sockets.TcpClient("$scxagent","22")).connected ) { $sshstatus = "OK"} Else { $sshstatus = "NOT OK" } } Catch { $sshstatus = "NOT OK" }  
         # WSMan Testing   
     If ( Test-WSMan -Port 1270 -ComputerName $scxagent -Authentication Basic -Credential $creds -UseSSL -ErrorAction SilentlyContinue ) { $wsmanstatus = 'OK' } Else { $wsmanstatus = 'NOT OK' }  
     $scxagenturi = "https://"+"$scxagent"+":1270/wsman"  
         # WinRM validation  
     Try { If ( winrm enumerate http://schemas.microsoft.com/wbem/wscim/1/cim-schema/2/SCX_Agent?__cimnamespace=root/scx -username:'YOUR_USERNAME_HERE' -password:'YOUR_PASSWORD_HERE' -r:$scxagenturi -auth:basic -skipCACheck -skipCNCheck -skiprevocationcheck -encoding:utf-8 ) { $winrmstatus = "OK" } Else { $winrmstatus = "NOT OK" } } Catch { $winrmstatus = "NOT OK" }  
     If ( $ECCount -gt "0" )        { $ecstatus = "NOT OK" } Else { $ecstatus = "OK" }  
         If ( $SUDOALL )            { $sudoallstatus = "OK" } Else { $sudoallstatus = "NOT OK" }  
     If ( $RPMLines -match "[0-9]" )    { $rpmstatus = "NOT OK" } Else { $rpmstatus = "OK" }  
     If ( $SSLConfig -match "scxsslconfig" ) { $SSLConfigStatus = "OK" } Else { $SSLConfigStatus = "NOT OK" }  
     If ( $CertConfig -match "$scxagent" -and $CertConfig -match $scxdomain) { $CertificateStatus = "OK" } Else { $CertificateStatus = "NOT OK" }  
         # Remove the ones that not match your cenario (For sudo config)  
     Write-Output "$scxagent | WSMAN : $wsmanstatus | SSH : $sshstatus | AgentPort : $AgentPortStatus | EC SUDOConfig : $ecstatus | RPM SUDOConfig : $rpmstatus | SUDOAll : $sudoallstatus | SCXConfig SUDOConfig : $SSLConfigStatus | Certificate : $CertificateStatus | WinRM : $winrmstatus"  
   }  
 }  
 Remove-SshSession -RemoveAll | Out-Null  


OpsMgr (SCOM) - (Bulk) Set Failover Management Server Powershell Script

I believe every SCOM Admin needs this.

This a script that for every agent you have, if it doesn't have a failover Management Server, it'll set it one.

NOTE : Please read comments inline before you run this

Enjoy!

 try{  
   [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.EnterpriseManagement.OperationsManager.Common") | Out-Null  
   [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.EnterpriseManagement.Core') | Out-Null  
   [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.EnterpriseManagement.OperationsManager') | Out-Null  
   [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.EnterpriseManagement.Runtime') | Out-Null  
 } Catch { '[OpsMgr] - DLL could not be loaded' }  
 try{  
   $MGConnSetting = New-Object Microsoft.EnterpriseManagement.ManagementGroupConnectionSettings("$env:computername")  
   $MG = New-Object Microsoft.EnterpriseManagement.ManagementGroup($MGConnSetting)  
 } Catch { '[OpsMgr] - Could not connect to Management Group' }  
 # Let's say .. you want to have a list of MS Servers you dont want to work as failover (in my case i don't want Network RP MS servers ... - It's up to you!) :)  
 $IgnoredMS = @('Server1','Server2')  
 # Criteria for every MS you got  
 $MSCriteria = New-Object Microsoft.EnterpriseManagement.Administration.ManagementServerCriteria("Name LIKE '%'")  
 # Let's say you only want to work on specific criteria  
 $MSCriteria = New-Object Microsoft.EnterpriseManagement.Administration.ManagementServerCriteria("Name LIKE 'OpsMgrServer%'")  
 $ManagementServers = ($MG.Administration.GetManagementServers($MSCriteria)).DisplayName | ? { $_ -notin $IgnoredMS }  
 $AgentCriteria = New-Object Microsoft.EnterpriseManagement.Administration.AgentManagedComputerCriteria("Name LIKE '%'")  
 $Agents = $MG.Administration.GetAgentManagedComputers($AgentCriteria) | ? { $_.PrimaryManagementServerName -in @($ManagementServers) }  
 # My logic is :  
 #     - Specific agent will only have a failover MS with the same domain  
 #    So you may need to edit code before running this.  
 Foreach ( $agent in $Agents ) {  
   If ( !($agent.GetFailoverManagementServers()) ) {  
     $PrimaryMS = $agent.PrimaryManagementServerName  
     $Domain = ($PrimaryMS -split "\.")[-2..-1] -join '.'  
     $FailoverMS = ($ManagementServers | Select-String -Pattern "$Domain" | ? { $_ -notin $PrimaryMS })[0]  
     $PrimaryMSCriteria = New-Object Microsoft.EnterpriseManagement.Administration.ManagementServerCriteria("Name = '$PrimaryMS'")  
     $PrimaryManagementServerID = $MG.Administration.GetManagementServers($PrimaryMSCriteria).ID  
     $PrimaryManagementServer = $MG.Administration.GetManagementServer($PrimaryManagementServerID)  
     $FailoverMSCriteria = New-Object Microsoft.EnterpriseManagement.Administration.ManagementServerCriteria("Name = '$FailoverMS'")  
     $FailoverMSIList = New-Object 'Collections.Generic.List[Microsoft.EnterpriseManagement.Administration.ManagementServer]'  
     $MG.Administration.GetManagementServers($FailoverMSCriteria) | % { $FailoverMSIList.Add($_) }  
     $agent.SetManagementServers($PrimaryManagementServer, $FailoverMSIList)  
   } Else { "$(($agent).DisplayName)" + ' has already a Failover MS configured' + "$($agent.GetFailoverManagementServers().DisplayName)" }  
 }  

Cheers!

Monday, May 23, 2016

OpsMgr (SCOM) - Solve the "An Item With The Same Key Has Already Been Added" error


For some reasons, like, having a bunch of procedures that include Unix/Linux servers into SCOM, you might get the 'An Item With The Same Key Has Already Been Added' error when you go to Administration - Unix/Linux Agents.

That means that you've duplicate unix/linux servers in your SCOM installation.

The only way to solve this is to find the duplicate entry, and delete them both.

To do that, you can't use the Get-SCXAgent, this will output unique values only - the only way to solve this is going to your OpsDB execute a querry (bellow), and foreach value you get, you need to run the 'get-scxagent "duplicate_value" | Remove-ScxAgent' cmdlet.

So, first things first.

The query :

 DECLARE @ClassName NVARCHAR(256)   
 DECLARE @CManagedTypeId UNIQUEIDENTIFIER   
 SET @ClassName = 'Microsoft.Unix.OperatingSystem'  
 SET @CManagedTypeId = (   
     SELECT ManagedTypeId  
     FROM ManagedType   
     WHERE TypeName = @ClassName )   
 SELECT  
     [ManagedEntityGenericView].[Id],   
     [ManagedEntityGenericView].[Name],   
     [ManagedEntityGenericView].[Path],   
     [ManagedEntityGenericView].[FullName],   
     [ManagedEntityGenericView].[LastModified],   
     [ManagedEntityGenericView].[TypedManagedEntityId],   
     NULL AS SourceEntityId   
 FROM  
     dbo.ManagedEntityGenericView   
 INNER JOIN (      
     SELECT DISTINCT [BaseManagedEntityId]   
     FROM dbo.[TypedManagedEntity] TME WITH(NOLOCK)   
     JOIN [dbo].[DerivedManagedTypes] DT ON DT.[DerivedTypeId] = TME.[ManagedTypeId]   
     WHERE  
         DT.[BaseTypeId] = @CManagedTypeId  
         AND TME.IsDeleted = 0 )  
 AS ManagedTypeIdForManagedEntitiesByManagedTypeAndDerived   
 ON ManagedTypeIdForManagedEntitiesByManagedTypeAndDerived.[BaseManagedEntityId] = [Id]   
 WHERE  
     [IsDeleted] = 0 AND  
     [TypedMonitoringObjectIsDeleted] = 0 AND  
     [ManagedEntityGenericView].[Path] IN (   
                                             SELECT [BaseManagedEntity].[Path]   
                                             FROM [BaseManagedEntity]   
                                             GROUP BY [BaseManagedEntity].[Path]   
                                             HAVING COUNT([BaseManagedEntity].[Path]) > 1   
                                          )  
 GROUP BY [ManagedEntityGenericView].[Id],   
     [ManagedEntityGenericView].[Name],   
     [ManagedEntityGenericView].[Path],   
     [ManagedEntityGenericView].[FullName],   
     [ManagedEntityGenericView].[LastModified],   
     [ManagedEntityGenericView].[TypedManagedEntityId]  
 HAVING COUNT([ManagedEntityGenericView].[Path]) > 1  

Now that you've the duplicate values to delete, just open a powershell prompt and run the follow cmdlet foreach duplicate value you've.

 get-scxagent "Your_Server" | Remove-ScxAgent  

And, your problem is solved.

Cheers,

Tuesday, April 12, 2016

OpsMgr (SCOM) - Visual Studio Report (System Drive Available Space)

Challanged by a local PFE friend to start using Visual Studio to create my reports, and keeping in mind i'm no SQL expert, and no Report-Builder greatest fan, i tried to make a costumers request on specific report they wanted (C: Drive available Space)!

So, since i'm kind new to Visual Studio, and to this scenario in general, i decided to make this how to, and obviously, share it here.

So! Let's open Visual Studio and click NEW!

And then :



You'll have a blank report, so we need to add a datasource (from where you'll get your data from!)
(Since we're using OpsMgrDW database...)



Then, add a dataset (it's a query!) :)

This one i used!

 SELECT DISTINCT VME.Path AS Agente, DISKs.SystemDiskUsed AS FreePercentage, DISKs.SystemDiskFreeMB AS FreeMB  
 FROM Perf.vPerfDaily AS PERF   
 INNER JOIN vManagedEntity AS VME ON PERF.ManagedEntityRowId = VME.ManagedEntityRowId   
 INNER JOIN vPerformanceRuleInstance AS PRI ON PRI.PerformanceRuleInstanceRowId = PERF.PerformanceRuleInstanceRowId   
 INNER JOIN vPerformanceRule AS PR ON PR.RuleRowId = PRI.RuleRowId   
 LEFT OUTER JOIN  
 (  
     SELECT ME.Path AS Computer,  
     AVG(CASE WHEN PR.CounterName = '% Free Space' THEN PERF.AverageValue END) AS SystemDiskUsed,   
     AVG(CASE WHEN PR.CounterName = 'Free Megabytes' THEN PERF.AverageValue END) AS SystemDiskFreeMB  
     FROM Perf.vPerfDaily AS PERF   
     INNER JOIN vPerformanceRuleInstance AS PRI ON PRI.PerformanceRuleInstanceRowId = PERF.PerformanceRuleInstanceRowId  
     INNER JOIN vPerformanceRule AS PR ON PR.RuleRowId = PRI.RuleRowId  
     INNER JOIN vManagedEntity AS ME ON PERF.ManagedEntityRowId = ME.ManagedEntityRowId  
     WHERE (PERF.DateTime > DATEADD(Day, - 7, GETDATE()))   
     AND (PR.CounterName = '% Free Space'   
     OR PR.CounterName = 'Free Megabytes') AND (PRI.InstanceName = 'C:')  
     GROUP BY ME.Path, PRI.InstanceName  
 ) AS DISKs ON VME.Path = DISKs.Computer  
 WHERE (PERF.DateTime > GETUTCDATE() - 2)   
 AND (PR.CounterName LIKE '% Free Space')  
 AND (PRI.InstanceName LIKE 'C:')  
 ORDER BY FreeMB  

















Then, add a matrix, with that dataset.



Add a logo you may want (Company logo perhaps!):


Change the expression value for the FreeMB (Just a tweak to look better!)


So, now, let's add some colour to it with a new row with a 'progress bar'.
First, add the row:


Then, insert the 'Data Bar'



For the 'progress bar' you added, change the values of its expression like : 


And the values of the series fill propreties, just to make it RED when is bellow a threshold you define.


As you did for the FreeMB field, do the same for the Free% :


Run the preview and you'll get :


And that's it! :)
Save the project, copy the RDL file into your Reporting Services server and schedule it!

In the future i'll make other posts like this, now that i'm a Visual Studio rookie :)

Cheers :)

Monday, April 11, 2016

OpsMgr (SCOM) - Powershell Get Servers in DA

I got this need to upgrade this script ( [SCOM & Orchestrator] - Alert Forwarding ) to include a new CustomField in the alarm properties with the DA name the object belongs to!
So i wondered if somehow was possible to know to which DA a specific server belongs to, and came up with this 'idea' (poweshell script!):

 Import-Module OperationsManager  
 $list = Get-SCOMClass -DisplayName "DA - NAME - HERE" | Get-SCOMClassInstance | %{$_.GetRelatedMonitoringObjects()} | %{$_.GetMonitoringRelationshipObjects()} | select SourceMonitoringObject, TargetMonitoringObject  
 ForEach ($i in $list) {  
     If ( $i.TargetMonitoringObject.DisplayName -eq 'SERVER_FQDN' ) {   
         $DA += $i.SourceMonitoringObject.DisplayName + ' || '  
     }  
}  

Enjoy :)

Friday, April 8, 2016

SCCM - Inventory SQL Query (Server Info)

The guy who manages CMDB wanted to update some CMDB fields, like Total Memory, CPU, Storage and OS Information on every registered server.

So, since SCCM is our configuration and also our "inventory" application, perhaps we could do some SQL-Query to save the day.

 SELECT DISTINCT   
    [RSYS].Netbios_Name0 AS [CI Name],  
      ROUND([OS].TotalVirtualMemorySize0 / CAST(1024 AS FLOAT),0) AS [Total RAM (GB)],  
      CASE WHEN ISNULL(SUM(CAST([LDISK].Size0 AS INT)),0) / 1024 > 0  
      THEN ISNULL(SUM(CAST([LDISK].Size0 AS INT)),0) / 1024  
      ELSE 1  
      END AS [Total Storage (GB)],  
      ROUND(SUM(CAST([LDISK].FreeSpace0 AS FLOAT) ) / CAST(1024 AS FLOAT),1) AS [Total Free Space (GB)],  
      [CPU].Name0 AS [CPU Info],  
      CASE WHEN [CPU].NumberOfCores0 IS NULL THEN '0' ELSE [CPU].NumberOfCores0 END AS Cores,  
      CASE WHEN [CPU].NumberOfLogicalProcessors0 IS NULL THEN '0' ELSE [CPU].NumberOfLogicalProcessors0 END AS [Logical Processors],  
      COUNT([CPU].ResourceID) AS [Number of CPUs],  
      [OS].Caption0 AS [OS]  
  FROM v_R_System RSYS  
      JOIN v_GS_PROCESSOR CPU on RSYS.ResourceID = CPU.ResourceID  
    JOIN v_GS_LOGICAL_DISK LDISK on RSYS.ResourceID = LDISK.ResourceID   
      JOIN v_GS_OPERATING_SYSTEM OS ON RSYS.ResourceID = OS.ResourceID  
 WHERE ISNULL([RSYS].Obsolete0, 0) <> 1  
 AND [LDISK].Size0 IS NOT NULL  
 AND ( [LDISK].DeviceID0 = 'C:' OR [LDISK].VolumeName0 = '/' )  
 GROUP BY [RSYS].Netbios_Name0, [OS].Caption0, [OS].TotalVirtualMemorySize0, [CPU].Name0, [LDISK].DeviceID0, [LDISK].VolumeName0, [LDISK].Size0,[CPU].NumberOfCores0,[CPU].NumberOfLogicalProcessors0  
 ORDER BY [RSYS].Netbios_Name0  

This will give you something like this :


See you soon!

Thursday, April 7, 2016

OpsMgr (SCOM) - Create Performance Charts in Powershell

It might be usefull for people who don't have Datazen or just want to create and manage their own costumized HTML views, or perhaps, for fun!

Powershell is one of the things i like the most while exploring everything that SCOM (and other System Center tools) can give to you, it just makes everything you want, a way or another.

So.

What about creating this in a Powershell script ?











This is the script that does all the magic :

!!NOTE!! : For the most problematic servers in specific groups i've i retrieve memory, cpu, and disk usage performance data, but, you change this logic as well as the performance counters.

Please attempt to the comments in-line so you can understand the logic :)
 [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization")  
 Import-Module OperationsManager  
 New-SCOMManagementGroupConnection -ComputerName "opsmgr.server.local" #Change here :)  
 $script:scriptpath = "C:\PS\OpsMgr_PerfCharts\images"  
   
 # Foreach Group above list the most problematic agents   
   
 $MyGroups = @()  
 $MyGroups += Get-SCOMGroup -DisplayName 'UNIX/Linux Computer Group'  
 $MyGroups += Get-SCOMGroup -DisplayName 'All Windows Computers'  
 $newTime = (Get-Date).AddHours(-24)  
 $Criteria = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringAlertCriteria("TimeRaised > `'$newTime`'")  
 $TransversalDepth = [Microsoft.EnterpriseManagement.Common.TraversalDepth]::Recursive  
 $TopAgents = @()  
 Foreach ( $Group in $MyGroups ) {  
   $TopObjAlerts = ($Group.GetMonitoringAlerts($Criteria , $TransversalDepth) | Group-Object MonitoringObjectPath | Sort-Object count -descending | select -first 5 Values).Values  
   Foreach ($i in $TopObjAlerts) {  
     $TopAgents += ($i -split ";")[0]  
   }  
   $TopAgents = $TopAgents.Split(";",[System.StringSplitOptions]::RemoveEmptyEntries)  
 }  
 $Count = 0  
 $Query = ""  
 Foreach ($Agent in $TopAgents) {  
   If ($TopAgents.Count -eq $Count ) {  
     $Query += "'" + $agent + "'" + ","  
   } Else { $Query += "'" + $agent + "'" }  
   $Count++  
 }  
   
 # Create SQL Query with the most problematic agents criteria   
   
 $Query = $Query -replace "''","','"  
   
 $SqlQuery = "  
 SELECT          
     Path,  
     DateTime,  
     CASE   
     WHEN vpr.ObjectName = 'Processor Information' THEN 'Processor'  
     WHEN vpr.ObjectName = 'LogicalDisk' THEN 'Logical Disk' ELSE vpr.ObjectName END as ObjectName,  
 CASE   
     WHEN vpr.CounterName = 'PercentMemoryUsed' OR vpr.CounterName = '% Available Memory' THEN '% Used Memory'   
     WHEN vpr.CounterName = '% Free Space' THEN '% Used Space'  
     ELSE vpr.CounterName END as CounterName,  
 CASE   
     WHEN len(vpri.InstanceName) < 1 THEN '_Total' ELSE vpri.InstanceName END as InstanceName,  
 CASE   
     WHEN vpr.CounterName = '% Available Memory' or vpr.CounterName = '% Free Space' THEN 100 - pvpr.AverageValue ELSE pvpr.AverageValue END as AverageValue,  
 100.00 as ComparisonValue  
 FROM   
     Perf.vPerfDaily pvpr WITH (NOLOCK)   
     inner join vManagedEntity vme WITH (NOLOCK) on pvpr.ManagedEntityRowId = vme.ManagedEntityRowId   
     inner join vPerformanceRuleInstance vpri WITH (NOLOCK) on pvpr.PerformanceRuleInstanceRowId = vpri.PerformanceRuleInstanceRowId   
     inner join vPerformanceRule vpr WITH (NOLOCK) on vpr.RuleRowId = vpri.RuleRowId   
 WHERE  
     Path in (  
         select   
             unix.DisplayName  
         from   
             vManagedEntity unix WITH (NOLOCK)   
             join vManagedEntityManagementGroup unixMG WITH (NOLOCK) ON unix.ManagedEntityRowId = unixMG.ManagedEntityRowId  
             join vManagedEntityType unixType WITH (NOLOCK) on unix.ManagedEntityTypeRowId = unixType.ManagedEntityTypeRowId  
         where   
             unixMG.ToDateTime Is Null   
             and (unixType.ManagedEntityTypeSystemName = 'Microsoft.Unix.Computer'   
             or unixType.ManagedEntityTypeSystemName = 'Microsoft.Windows.Computer' )  
             AND vpr.ObjectName <> 'Process'  
             AND unix.DisplayName IN ( $Query )  
     )  
 AND vpr.CounterName in ('% Processor Time','PercentMemoryUsed','% Available Memory','% Free Space')  
 AND vpri.InstanceName in ('_Total','C:','/','')  
 AND DATEDIFF(DAY, pvpr.DateTime, GETDATE()) < 8  
 "  
   
 $SqlQuery > $env:TEMP\query.sql  
   
 $SqlQuery = Get-Content $env:TEMP\query.sql  
   
 # Create connection to SCOMDW Database   
   
 $script:SQLServer = "SCOMDBSERVER\SCOMDW"  
 $script:SQLDBName = "OperationsManagerDW"  
 $script:connString = "Data Source=$SQLServer;Initial Catalog=$SQLDBName;Integrated Security = True"  
 $script:connection = New-Object System.Data.SqlClient.SqlConnection($connString)  
 $connection.Open()  
 $sqlcmd = $connection.CreateCommand()  
 $sqlcmd.CommandText = $SqlQuery  
 $results = $sqlcmd.ExecuteReader()  
 $script:table = new-object “System.Data.DataTable”  
 $script:table.Load($results)  
   
 # Lets GRAPH!  
   
 $Servers = ($table | Select -Unique Path).Path  
 # 1 Graph foreach Agent   
 Foreach ( $Server in $Servers ) {  
   $Server = ($Server -split '\.')[0]  
   $GraphName = $Server  
   # Create CHART   
   $GraphName = New-object System.Windows.Forms.DataVisualization.Charting.Chart  
   $GraphName.Width = 900  
   $GraphName.Height = 300  
   $GraphName.BackColor = [System.Drawing.Color]::White  
   # CHART Title  
   [void]$GraphName.Titles.Add("$Server - Performance Data")  
   $GraphName.Titles[0].Font = "Arial,10pt"  
   $GraphName.Titles[0].Alignment = "MiddleCenter"  
   # CHART Area and X/Y sizes   
   $chartarea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea  
   $chartarea.Name = "ChartArea"  
   $chartarea.AxisY.Title = "$CounterName"  
   $chartarea.AxisX.Title = "Time"  
   $chartarea.AxisY.Maximum = 100  
   $chartarea.AxisY.Interval = 10  
   $chartarea.AxisX.Interval = 1  
   $GraphName.ChartAreas.Add($chartarea)  
   # CHART Legend  
   $legend = New-Object system.Windows.Forms.DataVisualization.Charting.Legend  
   $legend.name = "Legend"  
   $GraphName.Legends.Add($legend)  
     # Line Colors  
     $num = 0  
   $LineColours = ('DarkBlue','Brown','DarkMagenta')  
   # Create 'n' SERIES (lines) for each COUNTER  
   Foreach ($Counter in $Counters) {  
     $LineColor = $LineColours[$num]  
     [void]$GraphName.Series.Add("$Counter")  
     $GraphName.Series["$Counter"].ChartType = "Line"  
     $GraphName.Series["$Counter"].BorderWidth = 2  
     $GraphName.Series["$Counter"].IsVisibleInLegend = $true  
     $GraphName.Series["$Counter"].chartarea = "ChartArea"  
     $GraphName.Series["$Counter"].Legend = "Legend"  
     $GraphName.Series["$Counter"].color = "$LineColor"  
     ForEach ($i in ($table | ? { $_.ObjectName -eq $Counter -and $_.Path -like "$Server*" }) ) {   
       $GraphName.Series["$Counter"].Points.addxy( ($i.DateTime -split ' ')[0] , $i.AverageValue )   
     }  
     $num++ # Next Color :)  
   }  
   $GraphName.SaveImage("$scriptpath\$Server.png","png") # Save the GRAPH as PNG  
 }  

Have fun! :)

Wednesday, April 6, 2016

OpsMgr (SCOM) - Alerts per group SQL Query (+) Datazen Dashboard

Since i showed up how to get it in a last post, a PFE friend of mine noticed something it migh consern, and to be honest i didn't pointed out because to me was not an issue, but, i understand it might me to some of you.

It doesn't give you a recursive membership alerts.
My older post will only give you object specific alerts on that group, "nothing else"!
So, i started to think on how i could help me (and you!) out with this "issue", and came up with this ideia.

First, i've showed how to do this by powershell.
All alerts for specific group, in a specific time range, and for specific severity - this were my filters.
But, how can i "translate" powershell into SQL ?
SQL-Profiler!
Basically when you run a powershell cmd-let like :
Get-SCOMAlert -Criteria (...)
You're connecting into OperationsManager database and making a query.

So, the query goes as follow:

First, i create a temp table i can put my "groups".

DECLARE @TMP_GROUP_TABLE table(BaseManagedEntityId uniqueidentifier, DisplayName varchar(50));  
 INSERT INTO @TMP_GROUP_TABLE  
 SELECT BaseManagedEntityId, DisplayName FROM basemanagedentity WITH (nolock)  
 WHERE DisplayName = ('Group Name Here')  
 OR DisplayName = ('Other Group')  
 OR DisplayName = ('Other Group')  
 OR DisplayName = ('Other Group')  
 OR DisplayName = ('Other Group')  
 OR DisplayName = ('Other Group')

Then, the magic query - it's already made up to join the temp table and add the group name in the end of it :) (Promise i'll make a post about SQL-Profiler!)

 DECLARE @LanguageCode1 varchar(3)  
 DECLARE @LanguageCode2 varchar(3)  
 DECLARE @ParentManagedEntityId uniqueidentifier  
 DECLARE @ResolutionState0 nvarchar(max)  
 DECLARE @Severity0 nvarchar(max)  
 DECLARE @TimeRaised0 datetime  
 SET @LanguageCode1='ENU' ; SET @LanguageCode2=NULL  
 SET @ResolutionState0=N'0' ; SET @Severity0=N'1'; SET @TimeRaised0='2016-04-04 00:00:00'  
 SELECT DISTINCT [AlertView].[Id],[AlertView].[Name],[AlertView].[Description],[AlertView].[MonitoringObjectId],[AlertView].[ClassId],[AlertView].[MonitoringObjectDisplayName],[AlertView].[MonitoringObjectName],  
                 [AlertView].[MonitoringObjectPath],[AlertView].[MonitoringObjectFullName],[AlertView].[IsMonitorAlert],[AlertView].[ProblemId],[AlertView].[RuleId],[AlertView].[ResolutionState],[AlertView].[Priority],  
                 [AlertView].[Severity],[AlertView].[Category],[AlertView].[Owner],[AlertView].[ResolvedBy],[AlertView].[TimeRaised],[AlertView].[TimeAdded],[AlertView].[LastModified],[AlertView].[LastModifiedBy],  
                 [AlertView].[TimeResolved],[AlertView].[TimeResolutionStateLastModified],[AlertView].[CustomField1],[AlertView].[CustomField2],[AlertView].[CustomField3],[AlertView].[CustomField4],[AlertView].[CustomField5],  
                 [AlertView].[CustomField6],[AlertView].[CustomField7],[AlertView].[CustomField8],[AlertView].[CustomField9],[AlertView].[CustomField10],[AlertView].[TicketId],[AlertView].[Context],[AlertView].[ConnectorId],  
                 [AlertView].[LastModifiedByNonConnector],[AlertView].[MonitoringObjectInMaintenanceMode],[AlertView].[MonitoringObjectHealthState],[AlertView].[ConnectorStatus],[AlertView].[RepeatCount],  
                 [MT_Computer].[NetbiosComputerName],[MT_Computer].[NetbiosDomainName],[MT_Computer].[PrincipalName],[AlertView].[LanguageCode],[AlertView].[AlertParams],[AlertView].[SiteName],  
                 [AlertView].[MaintenanceModeLastModified],[AlertView].[StateLastModified],[AlertView].[TfsWorkItemId],[AlertView].[TfsWorkItemOwner],  
                 [TEMP].DisplayName AS GroupName  
 FROM dbo.fn_AlertView(@LanguageCode1, @LanguageCode2) AS AlertView   
 LEFT OUTER JOIN dbo.MT_Computer ON AlertView.TopLevelHostEntityId = MT_Computer.BaseManagedEntityId  
 INNER JOIN dbo.RecursiveMembership AS RM ON AlertView.MonitoringObjectId = RM.ContainedEntityId   
 INNER JOIN @TMP_GROUP_TABLE AS TEMP ON RM.ContainerEntityId = TEMP.BaseManagedEntityId  
 WHERE (AlertView.[ResolutionState] = @ResolutionState0   
 AND AlertView.[Severity] >= @Severity0   
 AND AlertView.[TimeRaised] > @TimeRaised0)   
 AND (((RM.ContainerEntityId IN ( SELECT BaseManagedEntityId FROM @TMP_GROUP_TABLE ) )))   
 ORDER BY [AlertView].[LastModified] DESC  

Since you got your data into place, let's make the Datazen Dashboard for your teams!


This is a 5 minutes dashboard, you can edit the query to set some "alert thresholds" and create a more "KPI" oriented alert view dashboad in Datazen.

Cheers,

Thursday, March 31, 2016

OpsMgr (SCOM) - Powershell Event Views Dashboard

I don't like the idea to have lot's of console connecting to my Management Servers, so i give my clients the webconsole link.
But, as you migh know, there's a bunch of limitiations, like "Event Views" don't show up.
So, i had the need to overcome this issue.

Solution was to put Powershell in a SCOM Dashboard.

First, create a new Powershell Grid Layout "Dashboard View" with one cell.
Configure it and paste this code :

 # This example is for a Rule i have for unexpected restart/shutdowns (EventID = 1074)  
 # You can change as you want!  
 $a = Get-SCOMManagementGroup  
 $b = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringEventCriteria "RuleId='e7c857e6-7654-5f89-ecdf-8f93325c83ee'"  
 $Events = $a.GetMonitoringEvents($b)  
 $i = 0  
 foreach ($Event in $Events) {  
   $EventDescription = 'User : ' + [string]$Event.Parameters[6] + ' || Type : ' + [string]$Event.Parameters[4] + ' || Reason : ' + [string]$Event.Parameters[5]  
   $TimeAdded = $Event.TimeAdded  
   $LoggingComputer = [string]$Event.LoggingComputer  
   $dataObject = $ScriptContext.CreateInstance("xsd://foo!bar/baz")  
   $dataObject["Id"]=$i.toString()  
   $dataObject["TimeAdded"]=$TimeAdded  
   $dataObject["LoggingComputer"]=$LoggingComputer  
   $dataObject["Description"]=$EventDescription  
   $ScriptContext.ReturnCollection.Add($dataObject)  
   $i++  
 }  

:) Enjoy!

Tuesday, March 29, 2016

OpsMgr (SCOM) - Network Resource Pool Members "Not Monitored"

Recently i disabled OMS on my environment.
But soon i had an issue that came up.
My network resource pool and my discovery servers (the same that are RP members) went grey.

I realized it was a wrong server configuration because i got lots of 1202 (New Management Pack with id:"ZXYZ", version:"A.B.C.D" conflicts with cached Management Pack. Condition indicates wrong server configuration) events on my servers.
I tried to follow this solution :
http://thoughtsonopsmgr.blogspot.pt/2013/07/getting-rid-of-eventid-1202-condition.html
More cores - No luck.
But then it got me thinking ... What if it's a pool/networking discovery problem caused by OMS de-integration ?
I've deleted the discovery rule, i deleted the RP, and created new ones, exactly the same as before, but new ones!
And, for my suprise, my management servers and RP went green again!

So, and for the record, i've got about 500 network devices to be discovered, and i don't know if it was a OMS thing or not that perhaps messed up with some networking configuration or not - But, it worked for me!

Perhpas no one is going to have this exact problem (i think...) but if someone do, hope this could help you out!

Friday, March 4, 2016

OpsMgr (SCOM) - Grey Agents

It's a problem of us all - true story.

But how can i easily get a list of them ?

Like this :

 New-SCOMManagementGroupConnection -ComputerName "localhost"  
 $Agents = Get-SCOMClass -name “Microsoft.SystemCenter.Agent"  
 $objects = Get-SCOMMonitoringObject -class:$Agents | where {$_.IsAvailable –eq $false}  
 ForEach ($object in $objects) {  
     $object.DisplayName  
 }  
My next step is to give you the reason! :)

SCCM - Security and Critical Updates | Datazen Dashboard

Recently i needed to provide customer a dashboard on the missing patches for every machines in the park - and because managers like the fancy datazen dashboards, and i also like the easy way we can build some dashboards - why not ?

First of all, created this datasource in Datazen CP :


 SELECT    dbo.v_R_System.Name0 AS 'Computername', dbo.v_UpdateInfo.Title AS 'Updatename', dbo.v_StateNames.StateName, dbo.v_UpdateInfo.InfoURL,  
         dbo.v_Update_ComplianceStatusAll.LastStatusCheckTime, dbo.v_UpdateInfo.DateLastModified, dbo.v_UpdateInfo.IsDeployed, dbo.v_UpdateInfo.IsSuperseded,   
         dbo.v_UpdateInfo.IsExpired, dbo.v_UpdateInfo.BulletinID, dbo.v_UpdateInfo.ArticleID, dbo.v_UpdateInfo.DateRevised,   
         catinfo.CategoryInstanceName as 'Vendor',  
     catinfo2.CategoryInstanceName as 'UpdateClassification',  
         COUNT(case when catinfo2.CategoryInstanceName like 'Security%' then '1' else NULL end ) IsSecurity,  
         COUNT(case when catinfo2.CategoryInstanceName like 'Critical%' then '1' else NULL end ) IsCritical  
 FROM    dbo.v_StateNames  
         INNER JOIN dbo.v_Update_ComplianceStatusAll  
         INNER JOIN dbo.v_R_System ON dbo.v_R_System.ResourceID = dbo.v_Update_ComplianceStatusAll.ResourceID  
         INNER JOIN dbo.v_UpdateInfo ON dbo.v_UpdateInfo.CI_ID = dbo.v_Update_ComplianceStatusAll.CI_ID ON dbo.v_StateNames.StateID = dbo.v_Update_ComplianceStatusAll.Status  
         INNER JOIN v_CICategories_All catall on catall.CI_ID = dbo.v_UpdateInfo.CI_ID  
         INNER JOIN v_CategoryInfo catinfo on catall.CategoryInstance_UniqueID = catinfo.CategoryInstance_UniqueID and catinfo.CategoryTypeName='Company'  
         INNER JOIN v_CICategories_All catall2 on catall2.CI_ID=dbo.v_UpdateInfo.CI_ID  
         INNER JOIN v_CategoryInfo catinfo2 on catall2.CategoryInstance_UniqueID = catinfo2.CategoryInstance_UniqueID and catinfo2.CategoryTypeName='UpdateClassification'  
 WHERE    (dbo.v_StateNames.TopicType = 500)  
 AND        (dbo.v_StateNames.StateName = 'Update is required')  
 AND        (dbo.v_R_System.Name0 IN   
           (SELECT TOP (100) PERCENT SD.Name0 AS 'Machine Name'  
             FROM    dbo.v_R_System AS SD INNER JOIN  
                     dbo.v_FullCollectionMembership AS FCM ON SD.ResourceID = FCM.ResourceID INNER JOIN  
                     dbo.v_Collection AS COL ON FCM.CollectionID = COL.CollectionID LEFT OUTER JOIN  
                     dbo.v_R_User AS USR ON SD.User_Name0 = USR.User_Name0 INNER JOIN  
                     dbo.v_GS_PC_BIOS AS PCB ON SD.ResourceID = PCB.ResourceID INNER JOIN  
                     dbo.v_GS_COMPUTER_SYSTEM AS CS ON SD.ResourceID = CS.ResourceID INNER JOIN  
                     dbo.v_RA_System_SMSAssignedSites AS SAS ON SD.ResourceID = SAS.ResourceID  
             ))  
 AND        ((catinfo2.CategoryInstanceName like 'Critical%' ) OR (catinfo2.CategoryInstanceName like 'Security%' ))  
 GROUP BY dbo.v_R_System.Name0 , dbo.v_UpdateInfo.Title, dbo.v_StateNames.StateName,   
         dbo.v_Update_ComplianceStatusAll.LastStatusCheckTime, dbo.v_UpdateInfo.DateLastModified, dbo.v_UpdateInfo.IsDeployed, dbo.v_UpdateInfo.IsSuperseded,   
         dbo.v_UpdateInfo.IsExpired, dbo.v_UpdateInfo.BulletinID, dbo.v_UpdateInfo.ArticleID, dbo.v_UpdateInfo.DateRevised,   
         catinfo.CategoryInstanceName, catinfo2.CategoryInstanceName, dbo.v_UpdateInfo.InfoURL  

Then created this dashboard :



The challange now is to make this possible to any group of collection we might want to.

I made a post about it ... give it a try:

http://itopstuff.blogspot.pt/2015/11/sccm-missing-updates-per-collection.html

:)

OpsMgr (SCOM) - Schedule Maintenance Mode

Scenario :
- On our company third-party CMDB Management Portal, people could register interventions on their servers, scheduling downtimes on their servers in SCOM, so it won't impact on KPI or Uptime.

Solution :
- Created a small database with a simple table.
- Created a PS1 that reads data from the above table and for every record that is not 'Processed' it schedules its maintenance on SCOM.

1) Create the database on your MSSQL Server and the table with the following schema :

 CREATE TABLE Scheduling (  
       ID int IDENTITY(1,1) PRIMARY KEY,  
       CI varchar(255),  
       Instance varchar(255),  
       Type varchar(255) NOT NULL,  
       Team varchar(255) NOT NULL,  
       StartTime SMALLDATETIME NOT NULL,  
       EndTime SMALLDATETIME NOT NULL,  
       Comment varchar(255) NOT NULL,  
       Status varchar(255) NOT NULL,  
 );  

2) Create two Runbooks like this :

2.1 ) InsertIntoDB
- Basicaly used for the Third-Party software to consume it as web-service so it can make new inserts in your MSSQL table.

 $SQLServer = "" #Your Server   
 $SQLDBName = "" #YourDBName  
 $connString = "Data Source=$SQLServer;Initial Catalog=$SQLDBName;Integrated Security = True"  
 $connection = New-Object System.Data.SqlClient.SqlConnection($connString)   
 $connection.Open()  
 $sqlcmd = $connection.CreateCommand()  
 # This Strange Characters will disapear when pasting it into Orchestrator :)  
 $SqlQuery = "INSERT INTO Scheduling (ci,citype,Team,StartTime,EndTime,Comment,Status) VALUES ('\`d.T.~Ed/{B65C878B-BF92-4D90-AEA1-E8E03B2813D6}.{27788CF1-0A35-47D4-AA0B-B06325A5C5D4}\`d.T.~Ed/','\`d.T.~Ed/{B65C878B-BF92-4D90-AEA1-E8E03B2813D6}.{B0B8E6F4-2245-4FA6-BB99-D626CCA011E7}\`d.T.~Ed/','\`d.T.~Ed/{B65C878B-BF92-4D90-AEA1-E8E03B2813D6}.{7E4E2E7D-6070-472F-AB9A-7331E13CD6CF}\`d.T.~Ed/','\`d.T.~Ed/{B65C878B-BF92-4D90-AEA1-E8E03B2813D6}.{35128BA4-9FB4-417F-BC9C-FE4F43EC884C}\`d.T.~Ed/','\`d.T.~Ed/{B65C878B-BF92-4D90-AEA1-E8E03B2813D6}.{6B2977E6-609B-4139-A3FD-CF7646C27C10}\`d.T.~Ed/','\`d.T.~Ed/{B65C878B-BF92-4D90-AEA1-E8E03B2813D6}.{B7CD14CD-6990-4ECD-9CAA-A5EDF9C6D73D}\`d.T.~Ed/','NOT PROCESSED');"  
 $sqlcmd.CommandText = $SqlQuery  
 $sqlcmd.CommandText = $SqlQuery  
 $results = $sqlcmd.ExecuteNonQuery()  

        2.2 ) OpsMgr MM
- Used to periodically put objects into maintenance.
- It selects unprocessed items with specific conditions.
- When processed it updates table with processed status so it wont process anymore.
         (Note) - I've got this small PS1 script before i start doing things - it's simple test connectivity to the database from the runbook server :

 If ( (new-object System.Net.Sockets.TcpClient("\`d.T.~Vb/{4D50CD67-31C0-48AA-A37E-2E017F6DC9F9}\`d.T.~Vb/","1433")).connected -eq $true ) {  
   $LOG = "DATABASE CONNECTION OK" } Else { $LOG = "DATABASE CONNECTION NOT OK" }  

This is the one that does the magic!

 try{
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.EnterpriseManagement.OperationsManager.Common") | Out-Null
    [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.EnterpriseManagement.Core') | Out-Null
    [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.EnterpriseManagement.OperationsManager') | Out-Null
    [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.EnterpriseManagement.Runtime') | Out-Null
} Catch { "" }  

 try{
     $script:MGConnSetting = New-Object Microsoft.EnterpriseManagement.ManagementGroupConnectionSettings('YOUR_OPS_MGR_SERVER')
     $script:MG = New-Object Microsoft.EnterpriseManagement.ManagementGroup($MGConnSetting)
 } Catch {  
     $Script:log += "Cannot connect to OpsMgr "  
     exit
 }
   
 $script:SQLServer = "MyMSSQLServer"  
 $script:SQLDBName = "DatabaseName"  
 $script:connString = "Data Source=$SQLServer;Initial Catalog=$SQLDBName;Integrated Security = True"  
 $script:connection = New-Object System.Data.SqlClient.SqlConnection($connString)  
 $script:Reason = [Microsoft.EnterpriseManagement.Monitoring.MaintenanceModeReason]::PlannedOther  
 $script:Transversal = [Microsoft.EnterpriseManagement.Common.TraversalDepth]::Recursive  
 $Script:log = "--------"  
 $script:class = ""  
 $ClassInstance = ""  
 $table = ""  
   
 Function SqlDataManagement {
    param($QueryType)
    Try { 
        $connection.Open()
    } Catch {
     $Script:log += "Cannot Open DB Connection"
     exit
    } # Open Database Connection 
    $sqlcmd = $connection.CreateCommand()
    If ($QueryType -eq 'update') {
        $SqlQuery = "UPDATE Scheduling SET Status = 'Processed' WHERE ID = $ID"
        $sqlcmd.CommandText = $SqlQuery
        $results = $sqlcmd.ExecuteNonQuery()
    } # Update Database
    If ($QueryType -eq 'update_not_found') {
        $SqlQuery = "UPDATE Scheduling SET Status = 'CI NOT FOUND' WHERE ID = $ID"
        $sqlcmd.CommandText = $SqlQuery
        $results = $sqlcmd.ExecuteNonQuery()
    } # Update Database
    If ($QueryType -eq 'select') {
        $SqlQuery = "SELECT * FROM Scheduling WHERE Status = 'Not Processed' AND DATEDIFF(MINUTE,GETDATE(),[StartTime]) BETWEEN -1 AND 0 AND DATEDIFF(MINUTE,[StartTime],[EndTime]) > 5"
        $sqlcmd.CommandText = $SqlQuery
        $results = $sqlcmd.ExecuteReader()
        $script:table = new-object “System.Data.DataTable”
        $script:table.Load($results)
    } # Select rows to manage
    $connection.Close()
}

Function Get-SCOMObjectbyClass([string]$ClassDisplayName,[string]$CI) {
    $script:ClassCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringClassCriteria("Name = '$ClassDisplayName'")
    $script:MonitoringClass = $MG.GetMonitoringClasses($ClassCriteria)
    $script:MOCriteria = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringObjectGenericCriteria("DisplayName LIKE '$CI%'")
    Try {
        $script:ClassInstance = ($MG.GetMonitoringObjects($MOCriteria, $MonitoringClass[0]))[0]
    } Catch { 
        $Script:log += "$CI not found or not belonging to $ClassDisplayName"
      }
}

Function Send-Email([string]$Status) { #Mail & HTML Stuff
    $Head = ""
    $Image = "C:\OpsMgr\MM\images\logo_detail.png"
    $att1 = new-object Net.Mail.Attachment($Image)
    $att1.ContentType.MediaType = “image/png”
    $att1.ContentId = “Attachment”
    $att1.ContentDisposition.Inline = $true
    $att1.ContentDisposition.DispositionType = “Inline”
    $body = "<img src='cid:Attachment' height='12%' width='12%'/><br/>"  
    $body += "<center><h5 style=color:#999999>SCOM - Schedule Maintenance Mode</center></h5>"
    If ( $Status -eq "OK" ) {
        $body += "CI       - $CI 
 "
        $body += "Inicio   - $StartTime 
"
        $body += "Fim      - $EndTime 
"
        $body += "Razão    - $Comment 
"
    } ElseIf ( $Status -eq "NOT OK" ) {
        $body += "CI       - $CI 
 "
        $body += ""
        $body += "Putting $CI in MM failed"
        $body += "Reason:
"
        $body += ""
        $body += "$LOG"
    }
    $smtpServer = "SMTP.SERVER"
    $smtpFrom = "FROM@ADDRESS.COM"
    $smtpTo = "TO@ADDRESS.COM"
    $messageSubject = "SCOM-ScheduleMaintenanceMode - $CI"
    $message = New-Object System.Net.Mail.MailMessage $smtpfrom, $smtpto
    $message.Subject = $messageSubject
    $message.IsBodyHTML = $true
 $message.Attachments.Add($att1)
    $message.Body = ConvertTo-Html -Body $body -Head $head
    $smtp = New-Object Net.Mail.SmtpClient($smtpServer)
    $smtp.Send($message)
}
SqlDataManagement -QueryType select

foreach ( $i in $table ) {
    $script:StartTime = (Get-Date -date ($i.StartTime).ToString()).ToUniversalTime()
    $script:EndTime = ($StartTime.AddMinutes(($i.EndTime - $StartTime).TotalMinutes)).ToUniversalTime()   
    $script:Comment = $i.Comment
    $script:ID = $i.ID
    $script:Team = $i.Team
    $script:Type = $i.Type
    $script:CI = $i.CI
    switch ( $Type ) {
        "NetworkDevice" { $script:Class = 'System.NetworkManagement.Node' }
        "Computer"      { $script:Class = 'System.Computer' }
    } # Switch to check which object class type it is | Add many as you may like or need.
    Get-SCOMObjectbyClass -ClassDisplayName "$script:Class" -CI $script:CI
    If ( $ClassInstance -ne $null -and ($ClassInstance.InMaintenanceMode) -ne $true ) {
        try {
            $ClassInstance.ScheduleMaintenanceMode($StartTime,$EndTime,$Reason,$Comment,$Transversal)
   $ClassInstance = ($MG.GetMonitoringObjects($MOCriteria, $MonitoringClass[0]))[0]
            If ( $ClassInstance.InMaintenanceMode -eq $true ) {
                SqlDataManagement -QueryType update
                Send-Email -Status "OK"
            }
            Else { $Script:log +=  = "Failed to put $CI in MM."
                   Send-Email -Status "NOT OK"           
            }
        } # Object in Maintenance Mode
        Catch { 
            $Script:log += "Exception while putting $CI in MM :" + "$_.Exception.Message"
            Send-Email -Status "NOT OK"
        } 
    } Else { 
        $Script:log += "ClassInstance ($ClassInstance) Not Found or already in Maintenance" 
        SqlDataManagement -QueryType update_not_found
        Send-Email -Status "NOT OK"
      }
}

$SCOrchLog = $script:log
Runbook Design (Insert) :



Runbook Design (Process) :



Later i'll post on how to tell the third-party could "consume" the Insert into DB runbook as webservice.

:)

OpsMgr (SCOM) - Alert Views without any console ?

Recently i got the need to put "Alert Views" on 4 different Teams TV's.

My first though was ... "WebConsole can't do the job ..."
So i remembered that PS1 could save my day!

Cons:

- SCOM Web Console too slow;
- You need IE;
- ... and silverlight;

Solution :

- Created a PS1 that for every different group i want gets latest 24h alerts (Warn/Crit);
- Foreach group i create an HTML file and put it on my favourite Web-Server;
- Created a Runbook that for a 90 seconds schedule runs the PS1;
      - You can also have a Scheduled Task for the Job;
- HTML has a meta tag that makes HTML refresh every 30 seconds;


 Import-Module OperationsManager  
 New-SCOMManagementGroupConnection -ComputerName "SCOMSERVER_GOES_HERE"  
 $MyGroups = @()  
 Foreach ($item in Get-Content C:\OpsMgr\WebAlertViews\conf\Groups.conf ) {  # Dont Forget to change this!
     $MyGroups += Get-SCOMGroup -DisplayName $item  
 }  
 $newTime = (Get-Date).AddHours(-24)  
 $Criteria = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringAlertCriteria("ResolutionState = 0 AND Severity >= 1 AND TimeRaised > `'$newTime`'")  
 $TransversalDepth = [Microsoft.EnterpriseManagement.Common.TraversalDepth]::Recursive  
 Foreach ( $Group in $MyGroups ) {  
     $Head = "<meta http-equiv='refresh' content='30'>"  
     $Head +="<style>"  
     $Head +="BODY{background-color:White;font-family:Verdana,sans-serif; font-size: x-small;}"  
     $Head +="TABLE{font-family: verdana,arial,sans-serif; font-size:12px; color:#333333; border-width: 1px; border-color: #666666; border-collapse: collapse;}"  
     $Head +="TH{border-width: 1px; padding: 8px; border-style: solid; border-color: #666666; background-color: #dedede;}"  
     $Head +="TD{border-width: 1px; padding: 8px; border-style: solid; width:auto;}"  
     $Head +="</style>"  
     $Body = "<br><br>"  
     $Body += "<img src='.\images\nos_logo_detail.png' height='12%' width='12%'>"  
     $Body += "<center><h1 style=color:#999999>.: Relatório SCOM - Alert View | $(($Group).DisplayName) :.</center>"  
     $Body += "<center><table>"  
     $Body += "<tr>"  
     $Body += "<td>Severity</td>"  
     $Body += "<td>Time Raised</td>"  
     $Body += "<td>Path</td>"  
     $Body += "<td>Name</td>"  
     $Body += "<td>DisplayName</td>"  
     $Body += "<td>Description</td>"  
     $Body += "</tr>"  
     $Alerts = $Group.GetMonitoringAlerts( $Criteria, $TransversalDepth )  
     Foreach ($Alert in $Alerts ) {  
         If ($Alert.Severity -eq 2) { $image = 'critical.png' }  # You need this files
         If ($Alert.Severity -eq 1) { $image = 'warning.png' }  
         $Body += "<tr>"  
         $Body += "<center><td><img src='.\images\$Image' height='25px' width='25px'></td></center>"  
         $Body += "<td>$(($alert).TimeRaised)</td>"  
         $Body += "<td>$(($alert).MonitoringObjectPath)</td>"    
         $Body += "<td>$(($alert).Name)</td>"  
         $Body += "<td>$(($alert).MonitoringObjectDisplayName)</td>"  
         $Body += "<td>$(($alert).Description)</td>"  
         $Body += "</tr>"  
     }      
     $Body += "</table></center>"  
     # I got the above replace because of "Unix/Linux Group!"
     $HTMLFileName = (($Group.DisplayName -replace '/','_') -replace ' ','') + '.html'  
     $HTML = ConvertTo-Html -Head $head -Body $Body  
     $HTML > \\MyWebServer\SiteName\AlertViews\AlertView_$HTMLFileName    
 }  

Tuesday, February 16, 2016

[SCOM & Orchestrator] - Alert Forwarding

Let's analyse this scenario:

You want SCOM to forward alerts or open alerts as incidents in a third-party software.

Cons:
SCOM have a limitation when it comes to process alerts;
Only "Notification Pool Members" can manage notifications;
If your channel/subscrition relationship is heavy, you might get a problem - unmanaged alerts.

Pros:
All the Cons! :)

Now, the idea : (Thanks to a PFE friend! MD Thank you!)
What about SCOM just mark the alerts with some kind of tag, and then let Orchestrator listening for that tag, and let Orchestrator do the rest ?
So, for every subscription i've i call a powershell script based channel with this code :
Param([string]$AlertId,[string]$subscriptionID)
Import-Module OperationsManager
$MySubId = $subscriptionID.toString()
$MyAlertId = $AlertID.toString()
$sub = (Get-SCOMNotificationSubscription -Id $MySubId).DisplayName
Get-SCOMAlert -Id $MyAlertId | Set-SCOMAlert -CustomField5 ":waiting" -CustomField6 $sub

On the other hand i've got a channel (command line) based, configured like this :
Full Path of the command line : C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe
Command line parameters : -Command "& '"C:\OpsMgr\Powershell\Notifications\_OpsMgr_Set-SCOMAlert.ps1"'" -alertID '$Data/Context/DataItem/AlertId$' -subscriptionID '$MPElement$'
Startup folder for the command line : C:\Windows\system32\WindowsPowerShell\v1.0\


And i've got this runbook on the other side (Orch):

1) Create alerts on the other side :

2) Close alerts on the other side : (If a SCOM Alert gets closed!)

In my case i send a SNMP Trap to my "central" alarm system with specific identifiers, and when i do it i mark the "-CustomField5" as Forwarded just to make sure and let everyone know that my alarm was processed.

If you have any questions, just let me know! :)

Cheers