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,

No comments:

Post a Comment