Use PowerShell to Automate SCOM Agent Installations

Use PowerShell to Automate SCOM Agent Installations

  • Comments 17
  • Likes

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 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hi Boe,

    Thanks for sharing the script. Will test as soon as possible in my OpsMgr environment.

    But the download link does not seem to work for me. This link is working gallery.technet.microsoft.com/.../SCOM-Agent-Installation-8c237afe

    /Stefan Stranger

    Senior Premier Field Engineer Microsoft

    @sstranger

  • @Stefan

    Right click download link and select 'Save Target As'

  • Hi Boe,

    thank you very much for sharing your thoughts (and script) with us!

    This is a rather complicated script, that I can hardly follow ( having no SCOM handy :-)

    But even having not the Microsoft.EnterpriseManagement.OperationsManager.Client on board, it is quite interesting to follow your thoughts and the script progress.

    A lot of PS goodies can be found here, like hash table, parsing XML, using here-strings to encapsulate HTML, sending email and matching text with regular expressions.

    Another aspect that relates to the last two blog articles is the way you work with cmdlets most of the time and throw in .Net objects like [regex] and [net.dns] where they are needed.

    That's the way ... I like it :-)

    Klaus (Schulte)

  • I cannot wait to see how workflows affect scripts like this.

  • @Stefan, the link has been corrected. Thanks for pointing it out.

  • @Stefan: Let me know what you think about the script. I am always interested in feedback so I can make changes where necessary.

    @K_Schulte: Thanks! Glad you like it! It was a lot of fun to write and put all of the pieces together to make everything work how I wanted it to. You are right, I definitely wanted to use PowerShell cmdlets wherever possible and resort to .Net only when needed.

    @coderaven: It will be exciting to see how workflows will affect not only this script, but many other scripts and modules that folks have written.

  • Boe

    BTW, you get an especially Geek Cred bonus for your Domain name "RIVENDELL" :)

    Sean

  • @Sean Thanks! Pretty easy to tell that I am a Lord of the Rings fan :)

  • I'm trying to use your script with our SCOM 2012 systems. It is exactly what I need: Scan for systems without a SCOM agent and then push the agent to the systems.

    So far it looks like there is an error in the script: At one point you set $NonSCOMTEMP and a couple of lines further down you use $NonSCOM. If I correct that it runs through till the line

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

    which causes an error: Cannot index into a null arry.

    I'm not sure if this script was supposed to work with SCOM 2012. Maybe it doesn't work because it was ment to be used with SCOM 2007?  

    Looking further into it I'm confused about the regex search pattern you are using since I cannot find the string "Machine Name:" in $alert at all. My powershell scripting knowledge is not very advanced, so I might be wrong her ;-)

  • @Marcus Kraemer

    This script was meant to be used for SCOM 2007 and has not been tested on SCOM 2012 so it is possible that there are differences that could be affecting the script from working properly. We might be able to tweak the regex pattern if we know where the server name is being held on the $Alert variable (if it is there at all) so it can successfully pull the server name and continue onward.

  • Hi Boe,

    Thanks for sharing the script.

    I run into the same problem as Marcus Kraemer, although I use SCOM 2007R2.

    Best regards,

  • @Marcus Kraemer &  Koen

    I've used this in a SCOM 2012 SP1 environment and have not been able to duplicate the issues that you have been seeing. It is probably an issue with the regex pattern, but cannot know for sure without seeing an example machine name that is failing.

  • Hi Boe, Thanks for posting this, if I can get it working it'll be exactly what I was looking for. I take it from your last post that you've successfully run this for OpsMgr 2012 SP1...what about R2? We have a 2012 SP1 environment in production and just stood up its new 2012 R2 replacement environment with essentially no managed systems at this point. In testing your script, I first used your initial section to get a list of all servers in AD. I then removed one server to test against an set this updated file as the $Exempt variable in the script. The script accurately determines that this one server is the only one to discover and install the agent on; however, I'm getting an error that "cannot compare "server name" because it is not IComparable" from line 106 "If ($DiscResults.custommonitoringobjects -gt 0) {". Any idea where I can look to hunt down the cause? Thanks!

  • Hi Boe, Figured it out...for some reason the copy of the script I downloaded was not quite right, but fortunately your code snippets on this page are correct. The line that was failing should have read like this "If ($DiscResults.custommonitoringobjects.count -gt 0) {" instead of this "If ($DiscResults.custommonitoringobjects -gt 0) {", not the missing .count at the end. Script runs like a champ now, thanks again for posting it!

  • @Stuart Douglas Glad you got it figured out! I was starting to look into this when I saw your second comment. I'll double check the script and update it if it is still showing the wrong code.