Wednesday, May 24, 2017

SCCM (ConfigMrg) - WannaCry Ransomware Compliance

WannaCry Ransomware made some damages worldwide, and still lots of doubts about how to check if your infrastructure is safe.
This is been my days lately.

Check if my whole servers are patched, giving management teams compliance reports, and so on.
Lots of online examples, this, yes, is just another one.

Disclaimer: Modify the T-SQL query and VBScript for the specific HotFixID's - in my case are for 2003-2016 windows servers.

So, SCCM query to check out the servers missing the Ransomware fix :


SELECT dbo.v_R_System.Name0 AS 'Computername', v_R_System.Full_Domain_Name0, dbo.v_UpdateInfo.Title AS 'Updatename', 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 as 'Vendor',   
      catinfo2.CategoryInstanceName as 'UpdateClassification'   
      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'   
      INNER JOIN v_CH_ClientSummary on v_CH_ClientSummary.ResourceID = v_R_System.ResourceID  
      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   
                                     WHERE (COL.Name like 'All Windows Servers')  
                                   )  
          )   
      AND ((catinfo2.CategoryInstanceName like 'Critical%' ) OR (catinfo2.CategoryInstanceName like 'Security%' ))   
      AND dbo.v_UpdateInfo.ArticleID in ('4012214','4012212','4012213','4012598')  
      AND v_CH_ClientSummary.ClientActiveStatus = 1  
      ORDER BY dbo.v_R_System.Name0  

And, a SCCM configuration item VBScript (some servers don't have powershell...! yes, there're a few ...!) :

 strComputer = "."  
 Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")  
 Set colItems = objWMIService.ExecQuery("Select * from Win32_QuickFixEngineering",,48)  
 Set objFSO=CreateObject("Scripting.FileSystemObject")  
 Set wshNetwork = WScript.CreateObject( "WScript.Network" )  
 strComputerName = wshNetwork.ComputerName  
 For Each objItem in colItems   
 If objItem.HotfixID = "KB4012214" or objItem.HotfixID = "KB4012213" or objItem.HotfixID = "KB4012212" or objItem.HotfixID = "KB4012598" then  
 wscript.echo "Compliant"  
 End If  
 Next  

So, now just make a configuration baseline or add this configuration item to your existing configuration baseline.

Hope this helps you guys out.

Thursday, November 17, 2016

OpsMgr (SCOM) - Maintenance Web-Portal (Beta version is here!)

As mentioned here I've decided to develop a web based interface to manage scheduled maintenance mode for SCOM Objects.

And finally, i came up with a first beta version of it.
I promise that as soon as possible I'll be releasing it here - i'm just cleaning up code and giving a more exhaust testing before i give this to you all.
I hope until next week I'm making this available.

So far, the specifications/functionalities of it are:

Search objects of classes Windows and Unix/Linux servers;
Set two types of maintenance mode :
- From now until 'x' minutes;
- From a specific date until 'x' minutes;
Check for the latest maintenance history of searched objects (agents);

1 ) Searching for agents/objects

It shows you the result and give you the possibility to Manage (Set-up maintenance mode for searched object) and to check on maintenance history (History button).



2 ) Manage button to setup Maintenance

Here you can either setup a 'right-now' maintenance or scheduled it in the future.
I'm using this Orchestrator Runbook solution I've developed a few months ago to handle future maintenance.



This is the the way you select your future start-time



Fill the information and Submit it!



You will get different outputs on success or failure!



And ... the result :




As you can see ...



3) History button

Since you click it you'll be given a pop-up with the latest maintenance information like bellow



And this is it! I hope you enjoy the idea and what came out of it, hope you could give me any suggestions and feedback about it.

Now what i would like to do in the future of it.

Further specifications/functionalities i would like to develop:

Windows authentication instead of SDK user in web.config file;
- At this point it's much easier to work like this, but it's of course more reliable to only have access to the objects you manage;
Edit maintenance mode:
- Able to stop current maintenance mode;
- Able to increase the end time of current maintenance mode;
Give the end-user the ability to choose specific classes of objects (SQL Databases, Clusters ...);
On main page give some stats about SCOM and SCOMDW performance in graphs;
(Any other feature I'm missing and you could give me the idea instead!)

Cheers!

Tuesday, November 15, 2016

[Update II] OpsMgr (SCOM) - Operations Manager Maintenance Web-Portal

Keeping this up to date.

Started to do this :


From now on, i'll be developing the scheduled part of the solution - as mentioned before, this will integrate with my previous solution (Orchestrator Runbook and a simple SQL Database)

I've already studied on how i'm passing the windows authentication and will be on my further to-do list.

Cheers,

[Update] OpsMgr (SCOM) - Operations Manager Maintenance Web-Portal

Since last post about the Operations Manager Maintenance Web-Portal, I've been editing code so i could publish the solution, and getting new functions to it.

This is getting a beta version! :)

So far:
- Added the SDK user credentials to Web.Config file;
- Created the Object Maintenance Mode History functionality;
- You can now search objects of classes instead of agents - still figuring out the best way to give user the possibility to choose or to limit class scope;

History button :



Details view



To do :
- User scope - still didn't think about it!
- Future Maintenance (I'll use this Orchestrator Runbook to do the job later!) - the layout is done, code is missing!


Friday, November 11, 2016

OpsMgr (SCOM) - Operations Manager Maintenance Web-Portal

I'll be introducing this post with a disclaimer - I'm not a programmer! Still, i'm an enthusiast about learning and have crazy ideas to occupy my time! :)

I've decided to develop a web-portal (asp/c#) so we could use a web page to put our servers into maintenance mode without having the need to open Operations Manager console - I'll be enhancing this project with other "tasks" that could fit in it - any suggestions, feel free to share!

So ... I came up with this :

1 ) Searching for agent



2 ) Click Manage to setup Maintenance



The future is already designed (missing back code to handle!)



3 ) Settings and Submit!



4) You will get different outputs on success or failure!



And ... the result :




As you can see ...





Further things to do on this project :
- Have a login page so we could have different server scopes - different users see/manage their own servers;
- Future Maintenance (I'll use this Orchestrator Runbook to do the job later!) - the layout is done, code is missing!
- Search for other classes, instead of agents (I'm evaluating the best way to do it!)
- I'll provide the solution to everyone, just finishing to edit and put the code better!

This is a Alpha version, but, as soon as possible i'll be releasing a beta version so you could test on your own environment - until that any suggestions are very welcome!

Hope you enjoy!

Thursday, September 8, 2016

OpsMgr (SCOM) - NTP Deviation Monitor (VBScript)

OpsMgr by default doesn't evaluate the deviation between the NTP server time and the server/workstation time, and since there are some critical services that require this deviation to be almost 0, i've decided to create a three-state monitor for this evaluation.

First, create a "Timed Script Three State Monitor" on the Authoring pane (Create a Monitor) - select "Unit Monitor"
Associate it to a brand new MP.



Then, target it to Windows Computer. I've decided to select "Configuration" to it's parent monitor (this is up to you!).
Also decided to NOT ENABLE IT by default.



Next, select the most effective schedule for you, i've decided to leave it to 5 minutes.



Fill the file name also as the script area, with this script :
(As you might notice, part of this code is from Nagios check check_time.vbs - credits go to the Author : Dmitry Vayntrub (dvayntrub@yahoo.com) )

 Dim oAPI, oBag  
 Set oAPI = CreateObject("MOM.ScriptAPI")  
 Set oBag = oAPI.CreatePropertyBag()  
 Set objWMISvc = GetObject( "winmgmts:\\.\root\cimv2" )  
 Set colItems = objWMISvc.ExecQuery( "Select * from Win32_ComputerSystem" )  
 For Each objItem in colItems  
   strComputerDomain = objItem.Domain  
 Next  
 Set objShell = CreateObject("Wscript.Shell")  
 strCommand = "%SystemRoot%\System32\w32tm.exe /monitor /nowarn /computers:" & strComputerDomain  
 set objProc = objShell.Exec(strCommand)  
 warn = "5"  
 crit = "10"  
 input = ""  
 strOutput = ""  
 Do While Not objProc.StdOut.AtEndOfStream  
     input = objProc.StdOut.ReadLine  
     If InStr(input, "NTP") Then  
         strOutput = strOutput & input  
     End If  
 Loop  
 Set myRegExp = New RegExp  
 myRegExp.IgnoreCase = True  
 myRegExp.Global = True  
 myRegExp.Pattern = " NTP: ([+-][0-9]+\.[0-9]+)s"  
 Set myMatches = myRegExp.Execute(strOutput)  
 result = ""  
 If myMatches(0).SubMatches(0) <> "" Then  
     result = myMatches(0).SubMatches(0)  
 End If  
 For Each myMatch in myMatches  
     If myMatch.SubMatches(0) <> "" Then  
             If abs(result) > Abs(myMatch.SubMatches(0)) Then  
                 result = myMatch.SubMatches(0)  
             End If  
     End If  
 Next  
 If result = "" Then  
     Err = 3  
     Status = "UNKNOWN"  
  ElseIf result > crit Then  
     Err = 2  
     status = "CRITICAL"  
  ElseIf result > warn Then  
     Err = 1  
     status = "WARNING"  
  Else  
     Err = 0  
     status = "OK"  
 End If  
 Call oBag.AddValue("NTPStatus",status)  
 Call oAPI.Return(oBag)  



Next, we'll set the expressions (Unhealthy, Degraded and Healthy).
As you might notice i populated the Property Bag with parameter name as "NTPStatus" so you need to name your Parameter Name as follow :







Let the configure health step as it is.

For last, configure the alert rising as follows :



Now you need to override the monitor to a group or class as you prefer, and this is what it would like:



And that's it!

Hope you enjoy!

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,