Use PowerShell to Automate SCOM Agent Installations

Summary: Guest blogger, Boe Prox, shows how to use Windows PowerShell to automate SCOM agent installations.

Microsoft Scripting Guy, Ed Wilson, is here. Last month, guest blogger, Boe Prox, wrote a series of blogs about WSUS. Today he is back to talk about SCOM.

Photo of Boe Prox

Boe Prox is currently a senior systems administrator with BAE Systems. He has been in the IT industry since 2003, and he has been working with Windows PowerShell since 2009. Boe looks to script whatever he can, whenever he can. He is also a moderator on the Hey, Scripting Guy! Forum. Check out his current projects published on CodePlex: PoshWSUS and PoshPAIG.
Boe’s blog: Learn PowerShell | Achieve More

Here’s Boe…

Recently, I was asked to write a script to run against our new SCOM servers that would automate the SCOM agent installation for servers that are being joined to the domain and provide a report on those that successfully installed and also for those that failed to install. This way our SCOM administrators have a report of new systems that are managed by the agents, and they will also have a way to troubleshoot and, if required, manually install the agents on the systems that report failures.

Although I was not completely familiar with SCOM like the SCOM admins are, I do know my way around Windows PowerShell, and I was able to put something together that would meet all of the requirements. So without further ado, let us dive into the script that I wrote, which is also available on the Script Repository: SCOM Agent Installation and Reporting Script.

Requirements for script

Our goal for this script is to query Active Directory for all servers, query SCOM for all currently monitored servers on the network, and then filter out all of the systems that are in Active Directory so we only have unmanaged servers to work with. We can then attempt to discover those servers (a requirement before we can push an agent installation), filter out failed discoveries so only the successes remain, and finally attempt the installation of the agent on the rest of the servers. Lastly, we need to generate a report that lists Successful Installations, Failed Installations, and Failed Discoveries, and email it to the system administrators. Simple enough with Windows PowerShell!

For this script to run properly and do what we need it to do, we need to make sure that we can connect to Active Directory to gather all of the servers. We also need to ensure that we are running the script from a server that has the SCOM snap-in available. You can find this by running the following command.

Get-PSSnapin –Registered

Image of command output 

You should see the following item listed with everything else: Microsoft.EnterpriseManagement.OperationsManager.Client

This means that the SCOM snap-in is installed, and we can run this script from the server without worrying about it failing.

Digging into the script

Let us take a look at the first parts of the script.

$VerbosePreference = ‘continue’

Function Get-Server {

    $strCategory = “computer”

    $strOS = “Windows*Server*”

    $objSearcher = [adsisearcher]””

    $objSearcher.Filter = (“(&(objectCategory=$strCategory)(OperatingSystem=$strOS))”)

    $objSearcher.pagesize = 10

    $objsearcher.sizelimit = 5000

    $objSearcher.PropertiesToLoad.Add(“dnshostname”) | Out-Null

    $objSearcher.Sort.PropertyName = “dnshostname”

    $colResults = $objSearcher.FindAll()

    foreach ($objResult in $colResults) {

        $objComputer = $objResult.Properties

        $objComputer.dnshostname

    }

}

Here, I set up the VerbosePreference to “Continue” so I can track what the script is doing and where it is, in case something goes wrong. It is always good to include some sort of verbose/debug output in your scripts, so that not only you, but whoever uses your script will know what is going on during its use. Using my Get-Server function is a quick and simple way to pull a list of servers from Active Directory, and I add the DNSHostName attribute that I can use in my comparison later on. I chose the DNSHostName attribute because it matches up with the data that I will later receive when performing the SCOM query.

###User Defined Parameters

Write-Verbose (“[{0}] Reviewing user defined parameters” -f (Get-Date))

#SCOM Management Server

$SCOMMgmtServer = ‘SCOMMGMT.rivendell.com’

#SCOM RMS Server

$SCOMRMS = “SCOMMGMT.rivendell.com”

#Systems to Exempt from SCOM

$Exempt = @(Get-Content Exempt.txt)

$Emailparams = @{

    To = ‘boeprox@rivendell.com’

    From = ‘SCOMAgentAudit@rivendell.com’

    SMTPServer =’Exch.rivendell.com’

    Subject = “SCOM Agent Audit”

    BodyAsHTML = $True

}

Here I have my “user defined” parameters, where you need to update the existing parameters that match your environment. Listed are places for the SCOM management and RMS server, in addition to an optional exempt parameter in case you have systems that you cannot (for various reasons) have the SCOM agent installed on. If you notice the $EmailParams parameter, you will see that it is actually a hash table of several parameters that will be used at the end of this script for an email notification. By the way, this is called “splatting.” Learn it, live it, love it.

#Get list of AD Servers

Write-Verbose (“[{0}] Getting list of servers from Active Directory” -f (Get-Date))

$ADServers = Get-Server | Where {$Exempt -NotContains $_}

 

#Initialize SCOM SnapIn

Write-Verbose (“[{0}] Loading SCOM SnapIn” -f (Get-Date))

Add-PSSnapin Microsoft.EnterpriseManagement.OperationsManager.Client -ErrorAction SilentlyContinue

 

#Make SCOM Connection

Write-Verbose (“[{0}] Connecting to SCOM RMS Server: {1}” -f (Get-Date),$SCOMRMS)

New-ManagementGroupConnection -ConnectionString $SCOMRMS | Out-Null

 

#Connect to SCOM Provider

Write-Verbose (“[{0}] Connecting to SCOM Provider” -f (Get-Date))

Push-Location ‘OperationsManagerMonitoring::’

Write-Verbose (“[{0}] Connecting to SCOM Server: {1}” -f (Get-Date),$SCOMMgmtServer)

$MgmtServer = Get-ManagementServer | Where {$_.Name -eq $SCOMMgmtServer}

 

#Get all SCOM Agent Servers

Write-Verbose (“[{0}] Gathering all SCOM managed systems” -f (Get-Date))

$SCOMServers = Get-Agent | Select -Expand NetworkName | Sort

 

#Compare list to find servers not in SCOM

Write-Verbose (“[{0}] Filtering out all Non SCOM managed systems to audit” -f (Get-Date))

$NonSCOMTEMP = @(Compare-Object -ReferenceObject $SCOMServers -DifferenceObject $ADServers | Where {

    $_.SideIndicator -eq ‘=>’

} | Select -Expand Inputobject)

Now we are starting to perform some actions to get this script rolling. First, I use my little function to grab a list of all of the servers in Active Directory and filter out all of the systems that I listed in my exempt list. The next part loads up the SCOM snap-in so we are able to make use of the SCOM cmdlets. Next, I make the connection to the SCOM management server that was specified earlier in the script. When we have that connection, I switch directories to the “OperationsManagerMonitoring::” provider, which is required to run the commands later in the script.  After all of this, I begin my query of SCOM for all servers currently being managed via the SCOM agent, and I use Compare-Object to filter out the servers from my Active Directory list that are already listed in SCOM. We now have our list of servers that we need to focus on to install the SCOM agent.

#Attempt to Discover Systems

Write-Verbose (“[{0}] Configuring SCOM discovery prior to use” -f (Get-Date))

$Discover = New-WindowsDiscoveryConfiguration -ComputerName $NonSCOM -PerformVerification -ComputerType “Server”

$Discover.ComputerNameDiscoveryCriteria.getComputernames() | ForEach {Write-Verbose (“{0}: Attempting to discover” -f $_)}

Write-Verbose (“[{0}] Beginning SCOM discovery” -f (Get-Date))

$DiscResults = Start-Discovery -WindowsDiscoveryConfiguration $Discover -ManagementServer $MgmtServer

 

#Check Alert history for failed Discoveries

Write-Verbose (“[{0}] Checking for failed Discoveries” -f (Get-Date))

$alerts = @(Get-Alert -Criteria “PrincipalName = ‘$SCOMMgmtServer’ AND MonitoringClassId=’ab4c891f-3359-3fb6-0704-075fbfe36710’`

AND Name=’An error occurred during computer verification from the discovery wizard'”) | Where {   

    #Look for unresolved alerts

    $_.ResolutionState -eq 0

}

Here I am setting up for my attempted discovery of the servers that need the SCOM agent installed.  I use my current collection of servers that is supplied to the ComputerName parameter for New-WindowsDiscoveryConfiguration, which I save to $Discover. I then supply this variable to the Start-Discovery cmdlet, and save the results of this discovery to $DiscResults, which looks something like this:

Image of command output

This can be used later when I prepare to push out the SCOM agent installations.

Now that I went through the discovery process, a check is performed against the SCOM management server by using Get-Alert. I supply the principal name of the management server, filter to look only for failed discoveries, and save any results that are found so they can be parsed later and added to a collection for reporting.

If ($Alerts.count -gt 0) {

    #Start processing the failed discovery alerts

    $alert = $alerts  | Select -Expand Parameters

    $Pattern = “Machine Name: ((\w+|\.)*)\s”

    $FailedDiscover = $alert | ForEach {   

        $Server = ([regex]::Matches($_,$Pattern))[0].Groups[1].Value

        Try {

            $ServerIP = ([net.dns]::Resolve($Server).AddressList[0])

        } Catch {

            $ServerIP = $Null

        }

        If (-Not ([string]::IsNullOrEmpty($Server))) {

            New-Object PSObject -Property @{

                Server = $Server

                Reason = $_

                IP = $ServerIP

            }

        }

    }

    <#

    Resolve the alerts for failed discoveries, otherwise we will have false positives that there were no failed discoveries.

    #>

    Write-Verbose (“[{0}] Resolving active alerts” -f (Get-Date))

    $Alerts | ForEach {

        Resolve-Alert -Alert $_ | Out-Null

    }

}

If failed discoveries are found in the alert log, the script digs out the Parameters property of the $alert collection, and the systems will get parsed from the log by using some regular expression magic. Then an attempt to get the IP address will be performed. The expanded Parameters property will look similar to this:

Computer verification failure for Machine Name: DC1.Rivendell.com is 0x800706BA. The RPC server is unavailable.

Any generated reports will be saved to the $FailedDiscover variable that will be sent in the email report at the end of the script. After this is done, all of the alerts that are found are resolved by the script.

If ($DiscResults.CustomMonitoringObjects.count -gt 0) {

    #Install Agent on Discovered Servers

    Write-Verbose (“[{0}] Beginning installation of SCOM Agent on discovered systems” -f (Get-Date))

    $DiscResults.custommonitoringobjects | ForEach {Write-Verbose (“{0}: Attempting Agent Installation” -f $_.Name)}

    $Results = Install-Agent -ManagementServer $MgmtServer -AgentManagedComputer: $DiscResults.custommonitoringobjects

Now for the installation of the systems that we were able to successfully discover! If you remember, I saved the results of the discovery to the $DiscResults variable. Now I am able to use that to supply the collection of systems for the agent installation by using the CustomMonitoringObjects property of the $DiscResults collection. Note that I have saved the results of this agent installation to $Results that will be parsed later in the script, and those results will be included in the email report. 

    #Check for failed installations

    $FailedInstall = @{}

    $SuccessInstall = @{}

    Write-Verbose (“[{0}] Checking for Failed and Successful installations” -f (Get-Date))

    $Results.MonitoringTaskResults | ForEach {

 

        If (([xml]$_.Output).DataItem.ErrorCode -ne 0) {

            #Failed Installation

            $FailedInstall[([xml]$_.Output).DataItem.PrincipalName] = `

            (([xml]$_.output).DataItem.Description.”#cdata-section” -split “\s{2,}”)[0]

        } Else {

            #Successful Installation

            $SuccessInstall[([xml]$_.Output).DataItem.PrincipalName] = `

            Get-Date ([xml]$_.output).DataItem.Time

        }

    }

}

And by later in the script, I mean now. I first create two empty hash tables that will hold the successes and failures that are found. The results of the agent installation were first split based on the error code that is in the XML data. From there, if a failure is detected, the code continues to parse the failure message from the XML and place it into the $FailedInstall hash table. A failed installation result will look similar to this:

Image of command output

The task will register as a success, so I have to dig into the output XML to pull the actual error code for the agent installation. If it is a successful installation, the system is added to the $SuccessInstall hash table, which will be sent in the email report at the end of the script.

$head = @”

<style>

    TABLE{background-color:LightYellow;border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}

    TH{border-width: 1px;padding: 5px;border-style: solid;border-color: black;}

    TD{border-width: 1px;padding: 5px;border-style: solid;border-color: black;}

</style>

“@

 

If ($SuccessInstall.Count -gt 0) {

Write-Verbose (“[{0}] Adding {1} Successful installations to report” -f (Get-Date), $SuccessInstall.Count)

$html1 = @”

<html>

    <body>

        <h5>

            <font color=’white’>

                Please view in html!

            </font>

        </h5>

        <h2>

            The following servers were found in Active Directory and had the SCOM Agent successfully installed:

        </h2>

    $($SuccessInstall.GetEnumerator() | Select Name, Value | Sort Name | ConvertTo-HTML -head $head)

    </body>

</html>

“@

} Else {

    $html1 = $Null

}

 

If ($FailedInstall.Count -gt 0) {

Write-Verbose (“[{0}] Adding {1} Failed installations to report” -f (Get-Date), $FailedInstall.Count,)

$html2 = @”

<html>

    <body>

        <h5>

            <font color=’white’>

                Please view in html!

            </font>

        </h5>

        <h2>

            The following servers are Active Directory and were discovered, but Failed to install the SCOM Agent:

        </h2>

    $($FailedInstall.GetEnumerator() | Select Name,Value | Sort Name | ConvertTo-HTML -head $head)

    </body>

</html>

“@

} Else {

    $html2 = $Null

}

 

If ($FailedDiscover.Count -gt 0) {

Write-Verbose (“[{0}] Adding {1} Failed Discoveries to report” –f (Get-Date), $FailedDiscover.Count)

$html3 = @”

<html>

    <body>

        <h5>

            <font color=’white’>

                Please view in html!

            </font>

        </h5>

        <h2>

            The following servers are Active Directory but Failed to be Discovered by SCOM:

        </h2>

    $($FailedDiscover | Sort Server | ConvertTo-HTML -head $head)

    </body>

</html>

“@

} Else {

    $html3 = $Null

}

 

If ($html1 -OR $html2 -OR $html3) {

    $Emailparams[‘Body’] = “$($Html1,$Html2,$Html3)”

} Else {

    $Emailparams[‘Body’] = @”

<html>

    <body>

        <h5>

            <font color=’white’>

                Please view in html!

            </font>

        </h5>

        <h2>

            All servers in Active Directory are currently being managed by SCOM Agents.

        </h2>

    </body>

</html>

“@

}

Write-Verbose (“[{0}] Sending Audit report to list of recipients.” -f (Get-Date))

Send-MailMessage @Emailparams

We are now at the end of the script where the data we have collected is compiled into HTML and added to the body of the email. Take note of the @EmailParams that is supplied to the Send-MailMessage cmdlet. This is “splatting” being used to supply all of the parameters to the cmdlet. Although I am sure that my HTML code could be a little better, it does well enough to provide a nice readable report to review. If there is nothing to report, an email will still go out. This is a reminder that if a report didn’t go out, it should be investigated for possible issues.

Script in action

Typically, this script is better run as a scheduled job to ensure that any server being brought into the domain receives the SCOM agent. But for this example, I am going to run it to show the verbose output that is generated and to show the email notification showing the HTML body.

Image of command output

The report that is emailed will look something like this, based on the data that was received during the duration of the script.

So there you go…

With a little research and testing against a platform that I was not all that familiar with at the time, I was able to put together a nice script. My script automated the installation of SCOM agents for new servers that were brought into the domain, and provided a report on the installations and failures.

~Boe

Thank you, Boe, for sharing. As always, it is an interesting and informative blog.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy