{"id":11161,"date":"2012-02-13T00:01:00","date_gmt":"2012-02-13T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2012\/02\/13\/use-powershell-to-automate-scom-agent-installations\/"},"modified":"2012-02-13T00:01:00","modified_gmt":"2012-02-13T00:01:00","slug":"use-powershell-to-automate-scom-agent-installations","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/use-powershell-to-automate-scom-agent-installations\/","title":{"rendered":"Use PowerShell to Automate SCOM Agent Installations"},"content":{"rendered":"<p><b>Summary<\/b>: Guest blogger, Boe Prox, shows how to use Windows PowerShell to automate SCOM agent installations.<\/p>\n<p>Microsoft Scripting Guy, Ed Wilson, is here. Last month, guest blogger, Boe Prox, wrote a <a href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/tags\/windows+powershell\/boe+prox\/wsus\/\" target=\"_blank\">series of blogs about WSUS<\/a>. Today he is back to talk about SCOM.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/6180.hsg-2-13-12-1.jpg\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/6180.hsg-2-13-12-1.jpg\" alt=\"Photo of Boe Prox\" title=\"Photo of Boe Prox\" \/><\/a><\/p>\n<p style=\"padding-left: 30px\">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 <a href=\"http:\/\/technet.microsoft.com\/en-us\/scriptcenter\/powershell.aspx\" target=\"_blank\">Windows PowerShell<\/a> since 2009. Boe looks to script whatever he can, whenever he can. He is also a moderator on the <a href=\"http:\/\/bit.ly\/scriptingforum\" target=\"_blank\">Hey, Scripting Guy! Forum<\/a>. Check out his current projects published on CodePlex: <a href=\"http:\/\/poshwsus.codeplex.com\/\" target=\"_blank\">PoshWSUS<\/a> and <a href=\"http:\/\/poshpaig.codeplex.com\/\" target=\"_blank\">PoshPAIG<\/a>.<br \/> <strong>Boe&rsquo;s blog<\/strong>: <a href=\"http:\/\/learn-powershell.net\/\" target=\"_blank\">Learn PowerShell | Achieve More<\/a><\/p>\n<p>Here&rsquo;s Boe&hellip;<\/p>\n<p>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.<\/p>\n<p>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: <a href=\"http:\/\/gallery.technet.microsoft.com\/scriptcenter\/SCOM-Agent-Installation-8c237afe\" target=\"_blank\">SCOM Agent Installation and Reporting Script<\/a>.<\/p>\n<h3>Requirements for script<\/h3>\n<p>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<b> Successful Installations<\/b>, <b>Failed Installations<\/b>, and <b>Failed Discoveries<\/b>, and email it to the system administrators. Simple enough with Windows PowerShell!<\/p>\n<p>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.<\/p>\n<p style=\"padding-left: 30px\">Get-PSSnapin &ndash;Registered<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/2804.hsg-2-13-12-2.jpg\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/2804.hsg-2-13-12-2.jpg\" alt=\"Image of command output\" title=\"Image of command output\" \/><\/a>&nbsp;<\/p>\n<p>You should see the following item listed with everything else: <b>Microsoft.EnterpriseManagement.OperationsManager.Client <\/b><\/p>\n<p>This means that the SCOM snap-in is installed, and we can run this script from the server without worrying about it failing.<\/p>\n<h3>Digging into the script<\/h3>\n<p>Let us take a look at the first parts of the script.<\/p>\n<p style=\"padding-left: 30px\">$VerbosePreference = &#8216;continue&#8217;<\/p>\n<p style=\"padding-left: 30px\">Function Get-Server {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $strCategory = &#8220;computer&#8221;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $strOS = &#8220;Windows*Server*&#8221;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $objSearcher = [adsisearcher]&#8221;&#8221;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $objSearcher.Filter = (&#8220;(&amp;(objectCategory=$strCategory)(OperatingSystem=$strOS))&#8221;)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $objSearcher.pagesize = 10<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $objsearcher.sizelimit = 5000<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $objSearcher.PropertiesToLoad.Add(&#8220;dnshostname&#8221;) | Out-Null<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $objSearcher.Sort.PropertyName = &#8220;dnshostname&#8221;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $colResults = $objSearcher.FindAll()<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; foreach ($objResult in $colResults) {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $objComputer = $objResult.Properties<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $objComputer.dnshostname<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p>Here, I set up the VerbosePreference to &ldquo;Continue&rdquo; 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 <b>Get-Server<\/b> function is a quick and simple way to pull a list of servers from Active Directory, and I add the <b>DNSHostName<\/b> attribute that I can use in my comparison later on. I chose the <b>DNSHostName<\/b> attribute because it matches up with the data that I will later receive when performing the SCOM query.<\/p>\n<p style=\"padding-left: 30px\">###User Defined Parameters<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Reviewing user defined parameters&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">#SCOM Management Server<\/p>\n<p style=\"padding-left: 30px\">$SCOMMgmtServer = &#8216;SCOMMGMT.rivendell.com&#8217;<\/p>\n<p style=\"padding-left: 30px\">#SCOM RMS Server<\/p>\n<p style=\"padding-left: 30px\">$SCOMRMS = &#8220;SCOMMGMT.rivendell.com&#8221;<\/p>\n<p style=\"padding-left: 30px\">#Systems to Exempt from SCOM<\/p>\n<p style=\"padding-left: 30px\">$Exempt = @(Get-Content Exempt.txt)<\/p>\n<p style=\"padding-left: 30px\">$Emailparams = @{<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; To = &#8216;boeprox@rivendell.com&#8217;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; From = &#8216;SCOMAgentAudit@rivendell.com&#8217;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; SMTPServer =&#8217;Exch.rivendell.com&#8217;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Subject = &#8220;SCOM Agent Audit&#8221;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; BodyAsHTML = $True<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p>Here I have my &ldquo;user defined&rdquo; 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 <i>$EmailParams<\/i> 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 &ldquo;splatting.&rdquo; Learn it, live it, love it.<\/p>\n<p style=\"padding-left: 30px\">#Get list of AD Servers<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Getting list of servers from Active Directory&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">$ADServers = Get-Server | Where {$Exempt -NotContains $_}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">#Initialize SCOM SnapIn<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Loading SCOM SnapIn&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">Add-PSSnapin Microsoft.EnterpriseManagement.OperationsManager.Client -ErrorAction SilentlyContinue<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">#Make SCOM Connection<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Connecting to SCOM RMS Server: {1}&#8221; -f (Get-Date),$SCOMRMS)<\/p>\n<p style=\"padding-left: 30px\">New-ManagementGroupConnection -ConnectionString $SCOMRMS | Out-Null<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">#Connect to SCOM Provider<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Connecting to SCOM Provider&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">Push-Location &lsquo;OperationsManagerMonitoring::&rsquo;<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Connecting to SCOM Server: {1}&#8221; -f (Get-Date),$SCOMMgmtServer)<\/p>\n<p style=\"padding-left: 30px\">$MgmtServer = Get-ManagementServer | Where {$_.Name -eq $SCOMMgmtServer}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">#Get all SCOM Agent Servers<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Gathering all SCOM managed systems&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">$SCOMServers = Get-Agent | Select -Expand NetworkName | Sort<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">#Compare list to find servers not in SCOM<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Filtering out all Non SCOM managed systems to audit&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">$NonSCOMTEMP = @(Compare-Object -ReferenceObject $SCOMServers -DifferenceObject $ADServers | Where {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $_.SideIndicator -eq &#8216;=&gt;&#8217;<\/p>\n<p style=\"padding-left: 30px\">} | Select -Expand Inputobject)<\/p>\n<p>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 &ldquo;OperationsManagerMonitoring::&rdquo; provider, which is required to run the commands later in the script. &nbsp;After all of this, I begin my query of SCOM for all servers currently being managed via the SCOM agent, and I use <b>Compare-Object<\/b> 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.<\/p>\n<p style=\"padding-left: 30px\">#Attempt to Discover Systems<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Configuring SCOM discovery prior to use&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">$Discover = New-WindowsDiscoveryConfiguration -ComputerName $NonSCOM -PerformVerification -ComputerType &#8220;Server&#8221;<\/p>\n<p style=\"padding-left: 30px\">$Discover.ComputerNameDiscoveryCriteria.getComputernames() | ForEach {Write-Verbose (&#8220;{0}: Attempting to discover&#8221; -f $_)}<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Beginning SCOM discovery&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">$DiscResults = Start-Discovery -WindowsDiscoveryConfiguration $Discover -ManagementServer $MgmtServer<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">#Check Alert history for failed Discoveries<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Checking for failed Discoveries&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">$alerts = @(Get-Alert -Criteria &#8220;PrincipalName = &#8216;$SCOMMgmtServer&#8217; AND MonitoringClassId=&#8217;ab4c891f-3359-3fb6-0704-075fbfe36710&#8217;`<\/p>\n<p style=\"padding-left: 30px\">AND Name=&#8217;An error occurred during computer verification from the discovery wizard'&#8221;) | Where {&nbsp;&nbsp;&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; #Look for unresolved alerts<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $_.ResolutionState -eq 0<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p>Here I am setting up for my attempted discovery of the servers that need the SCOM agent installed.&nbsp; I use my current collection of servers that is supplied to the <i>ComputerName<\/i> parameter for <b>New-WindowsDiscoveryConfiguration<\/b>, which I save to <b>$Discover<\/b>. I then supply this variable to the <b>Start-Discovery<\/b> cmdlet, and save the results of this discovery to <b>$DiscResults<\/b>, which looks something like this:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/6724.hsg-2-13-12-3.jpg\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/6724.hsg-2-13-12-3.jpg\" alt=\"Image of command output\" title=\"Image of command output\" \/><\/a><\/p>\n<p>This can be used later when I prepare to push out the SCOM agent installations.<\/p>\n<p>Now that I went through the discovery process, a check is performed against the SCOM management server by using <b>Get-Alert<\/b>. 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.<\/p>\n<p style=\"padding-left: 30px\">If ($Alerts.count -gt 0) {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; #Start processing the failed discovery alerts<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $alert = $alerts&nbsp; | Select -Expand Parameters<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $Pattern = &#8220;Machine Name: ((\\w+|\\.)*)\\s&#8221;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $FailedDiscover = $alert | ForEach {&nbsp;&nbsp;&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Server = ([regex]::Matches($_,$Pattern))[0].Groups[1].Value<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Try {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ServerIP = ([net.dns]::Resolve($Server).AddressList[0])<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } Catch {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ServerIP = $Null<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; If (-Not ([string]::IsNullOrEmpty($Server))) {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; New-Object PSObject -Property @{<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Server = $Server<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Reason = $_<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IP = $ServerIP<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; &lt;#<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Resolve the alerts for failed discoveries, otherwise we will have false positives that there were no failed discoveries.<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; #&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Write-Verbose (&#8220;[{0}] Resolving active alerts&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $Alerts | ForEach {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Resolve-Alert -Alert $_ | Out-Null<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p>If failed discoveries are found in the alert log, the script digs out the <b>Parameters<\/b> property of the <b>$alert<\/b> 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 <b>Parameters<\/b> property will look similar to this:<\/p>\n<p style=\"padding-left: 30px\"><i>Computer verification failure for Machine Name: DC1.Rivendell.com is 0x800706BA. The RPC server is unavailable.<\/i><\/p>\n<p>Any generated reports will be saved to the <b>$FailedDiscover<\/b> 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.<\/p>\n<p style=\"padding-left: 30px\">If ($DiscResults.CustomMonitoringObjects.count -gt 0) {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; #Install Agent on Discovered Servers<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Write-Verbose (&#8220;[{0}] Beginning installation of SCOM Agent on discovered systems&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $DiscResults.custommonitoringobjects | ForEach {Write-Verbose (&#8220;{0}: Attempting Agent Installation&#8221; -f $_.Name)}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $Results = Install-Agent -ManagementServer $MgmtServer -AgentManagedComputer: $DiscResults.custommonitoringobjects<\/p>\n<p>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 <b>$DiscResults<\/b> variable. Now I am able to use that to supply the collection of systems for the agent installation by using the <b>CustomMonitoringObjects<\/b> property of the <b>$DiscResults<\/b> collection. Note that I have saved the results of this agent installation to <b>$Results<\/b> that will be parsed later in the script, and those results will be included in the email report.&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; #Check for failed installations<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $FailedInstall = @{}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $SuccessInstall = @{}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; Write-Verbose (&#8220;[{0}] Checking for Failed and Successful installations&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $Results.MonitoringTaskResults | ForEach {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; If (([xml]$_.Output).DataItem.ErrorCode -ne 0) {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #Failed Installation<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $FailedInstall[([xml]$_.Output).DataItem.PrincipalName] = `<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (([xml]$_.output).DataItem.Description.&#8221;#cdata-section&#8221; -split &#8220;\\s{2,}&#8221;)[0]<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } Else {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #Successful Installation<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $SuccessInstall[([xml]$_.Output).DataItem.PrincipalName] = `<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Get-Date ([xml]$_.output).DataItem.Time<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p>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 <b>$FailedInstall<\/b> hash table. A failed installation result will look similar to this:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/4341.hsg-2-13-12-4.jpg\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/4341.hsg-2-13-12-4.jpg\" alt=\"Image of command output\" title=\"Image of command output\" \/><\/a><\/p>\n<p>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 <b>$SuccessInstall<\/b> hash table, which will be sent in the email report at the end of the script.<\/p>\n<p style=\"padding-left: 30px\">$head = @&#8221;<\/p>\n<p style=\"padding-left: 30px\">&lt;style&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; TABLE{background-color:LightYellow;border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; TH{border-width: 1px;padding: 5px;border-style: solid;border-color: black;}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; TD{border-width: 1px;padding: 5px;border-style: solid;border-color: black;}<\/p>\n<p style=\"padding-left: 30px\">&lt;\/style&gt;<\/p>\n<p style=\"padding-left: 30px\">&#8220;@<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">If ($SuccessInstall.Count -gt 0) {<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Adding {1} Successful installations to report&#8221; -f (Get-Date), $SuccessInstall.Count)<\/p>\n<p style=\"padding-left: 30px\">$html1 = @&#8221;<\/p>\n<p style=\"padding-left: 30px\">&lt;html&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; &lt;body&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;h5&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;font color=&#8217;white&#8217;&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Please view in html!<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/font&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/h5&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;h2&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; The following servers were found in Active Directory and had the SCOM Agent successfully installed:<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/h2&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $($SuccessInstall.GetEnumerator() | Select Name, Value | Sort Name | ConvertTo-HTML -head $head)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; &lt;\/body&gt;<\/p>\n<p style=\"padding-left: 30px\">&lt;\/html&gt;<\/p>\n<p style=\"padding-left: 30px\">&#8220;@<\/p>\n<p style=\"padding-left: 30px\">} Else {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $html1 = $Null<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">If ($FailedInstall.Count -gt 0) {<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Adding {1} Failed installations to report&#8221; -f (Get-Date), $FailedInstall.Count,)<\/p>\n<p style=\"padding-left: 30px\">$html2 = @&#8221;<\/p>\n<p style=\"padding-left: 30px\">&lt;html&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; &lt;body&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;h5&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;font color=&#8217;white&#8217;&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Please view in html!<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/font&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/h5&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;h2&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; The following servers are Active Directory and were discovered, but Failed to install the SCOM Agent:<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/h2&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $($FailedInstall.GetEnumerator() | Select Name,Value | Sort Name | ConvertTo-HTML -head $head)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; &lt;\/body&gt;<\/p>\n<p style=\"padding-left: 30px\">&lt;\/html&gt;<\/p>\n<p style=\"padding-left: 30px\">&#8220;@<\/p>\n<p style=\"padding-left: 30px\">} Else {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $html2 = $Null<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">If ($FailedDiscover.Count -gt 0) {<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Adding {1} Failed Discoveries to report&#8221; &ndash;f (Get-Date), $FailedDiscover.Count)<\/p>\n<p style=\"padding-left: 30px\">$html3 = @&#8221;<\/p>\n<p style=\"padding-left: 30px\">&lt;html&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; &lt;body&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;h5&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;font color=&#8217;white&#8217;&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Please view in html!<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/font&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/h5&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;h2&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; The following servers are Active Directory but Failed to be Discovered by SCOM:<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/h2&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $($FailedDiscover | Sort Server | ConvertTo-HTML -head $head)<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; &lt;\/body&gt;<\/p>\n<p style=\"padding-left: 30px\">&lt;\/html&gt;<\/p>\n<p style=\"padding-left: 30px\">&#8220;@<\/p>\n<p style=\"padding-left: 30px\">} Else {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $html3 = $Null<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">If ($html1 -OR $html2 -OR $html3) {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $Emailparams[&#8216;Body&#8217;] = &#8220;$($Html1,$Html2,$Html3)&#8221;<\/p>\n<p style=\"padding-left: 30px\">} Else {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $Emailparams[&#8216;Body&#8217;] = @&#8221;<\/p>\n<p style=\"padding-left: 30px\">&lt;html&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; &lt;body&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;h5&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;font color=&#8217;white&#8217;&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Please view in html!<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/font&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/h5&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;h2&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; All servers in Active Directory are currently being managed by SCOM Agents.<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/h2&gt;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; &lt;\/body&gt;<\/p>\n<p style=\"padding-left: 30px\">&lt;\/html&gt;<\/p>\n<p style=\"padding-left: 30px\">&#8220;@<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">Write-Verbose (&#8220;[{0}] Sending Audit report to list of recipients.&#8221; -f (Get-Date))<\/p>\n<p style=\"padding-left: 30px\">Send-MailMessage @Emailparams<\/p>\n<p>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 <b>@EmailParams<\/b> that is supplied to the <b>Send-MailMessage<\/b> cmdlet. This is &ldquo;splatting&rdquo; 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&rsquo;t go out, it should be investigated for possible issues.<\/p>\n<h3>Script in action<\/h3>\n<p>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.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3240.hsg-2-13-12-5.jpg\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3240.hsg-2-13-12-5.jpg\" alt=\"Image of command output\" title=\"Image of command output\" \/><\/a><\/p>\n<p>The report that is emailed will look something like this, based on the data that was received during the duration of the script.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/0131.SCOMReport.jpg\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/0131.SCOMReport.jpg\" border=\"0\" alt=\"\" \/><\/a><\/p>\n<p>So there you go&hellip;<\/p>\n<p>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.<\/p>\n<p>~Boe<\/p>\n<p>Thank you, Boe, for sharing. As always, it is an interesting and informative blog.<\/p>\n<p>I invite you to follow me on <a href=\"http:\/\/bit.ly\/scriptingguystwitter\" target=\"_blank\">Twitter<\/a> and <a href=\"http:\/\/bit.ly\/scriptingguysfacebook\" target=\"_blank\">Facebook<\/a>. If you have any questions, send email to me at <a href=\"mailto:scripter@microsoft.com\" target=\"_blank\">scripter@microsoft.com<\/a>, or post your questions on the <a href=\"http:\/\/bit.ly\/scriptingforum\" target=\"_blank\">Official Scripting Guys Forum<\/a>. See you tomorrow. Until then, peace.<\/p>\n<p><b>Ed Wilson, Microsoft Scripting Guy<\/b>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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. Boe Prox is currently a senior systems administrator with BAE Systems. [&hellip;]<\/p>\n","protected":false},"author":596,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[162,56,321,3,45],"class_list":["post-11161","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-boe-prox","tag-guest-blogger","tag-scom-2007-r2","tag-scripting-guy","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>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. Boe Prox is currently a senior systems administrator with BAE Systems. [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/11161","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/596"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=11161"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/11161\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=11161"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=11161"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=11161"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}