Check_MK Environmental Monitoring: Room Alert 4E

One of our Network Engineers needed some monitoring (internal temp, external temp, and external humidity) added for an AVTech Room Alert 4E unit, preferably using a check_MK plugin. Couldn’t find one in any repos or in git, so decided to add this to a growing collection of plugins I’ve been working on. Another request from the same team: MinuteMan UPS units. I’ll be adding to my check_mk_bag git repo periodically with any new, custom check plugins. I’ll also try to get these on Check_MK’s Exchange. For now, here’s some environmental monitors, of course with perfdata output — gotta have the metrics available for trending!

As typical with check_MK checks, this supports auto-inventory (scan function configured):

ra4e_sensor_temp_defaultlevels = (28, 32)

def inventory_ra4e_sensor_temp(info):
    inventory = []
    for tempc, tempf, desc in info:
        inventory.append( (desc, "ra4e_sensor_temp_defaultlevels") )
    import pprint ; pprint.pprint(info)
    return inventory

def check_ra4e_sensor_temp(item, params, info):
    for tempc, tempf, desc in info:
        if desc == item:
            warn, crit = params
	    temp = (int(tempc)/100)
	    tempf = (int(tempf)/100)
            if tempc != "" and tempf != "":
                infotext = "%.1f " % tempf + "F (%.1f C)" % temp + " (warn/crit at %.1f/%.1f " % (warn, crit) + "C)"
                perfdata = [ ( "temperature", temp, warn, crit ) ]
                if temp >= crit:
                    return (2, "Temperature is: " + infotext, perfdata)
                elif temp >= warn:
                    return (1, "Temperature is: " + infotext, perfdata)
                    return (0, "Temperature is: " + infotext, perfdata )
                return (3, "Sensor is offline")
    return (3, "Sensor not found")

check_info["ra4e_sensor_temp"] = {
    'check_function':          check_ra4e_sensor_temp,
    'inventory_function':      inventory_ra4e_sensor_temp,
    'service_description':     'Temperature %s',
    'has_perfdata':            True,
    'snmp_info':               (
        ".", [
            1, #digital-sen1 - Degrees in Celsius
            2, #digital-sen1 - Degrees in Fahrenheit
            6, #digital-sen1 - Description 
    'snmp_scan_function':      \
         lambda oid: "Room Alert" in oid("."),
    'group':                   'room_temperature',

Making Cisco Identity Firewall, CDA, and ISE play nice

As of Cisco CDA Patch 2, identity mappings provided via Cisco ISE are natively supported. This means you can authenticate against ISE, which may in turn authenticate against LDAP or Active Directory, and subsequently notify one or more Cisco CDA servers that a new user-to-IP mapping exists. Cisco accomplishes this exchange of authenticated identities via syslog messages. ISE is configured to forward syslog messages to the CDA server(s), and the CDA server(s) have the sending ISE server(s) configured as a syslog “client.”

Visio of Cisco IDFW Identity Digestion from ISE
Cisco IDFW Identity Digestion from ISE via Syslog-NG

We didn’t wait for this native support at our organization since we needed identities consumed by Cisco WLC via ISE to be available before the general release of Patch 2. How’d we manage this? After successful authentication via ISE, we would forward syslog RADIUS accounting messages to a syslog-ng server. We’d filter only the messages we needed and pass off the necessary information to a Python app listening for input on stdin. This app digests the information and creates its own RADIUS accounting packet that gets forwarded to an array of CDA and/or AD agent servers. This method works with their legacy AD agent server and non-Patch 2 CDA appliances.

Why aren’t we using the native solution? We recently encountered problems with proper user-to-IP mappings being overwritten by machine-to-IP mappings forwarded from ISE. Since we’re using ISE for user and machine auth, it does make sense to see user and machine mappings, but because only one user can be affiliated with any one IP address, the desired user-to-IP mappings occasionally get overwritten with machine names or MAC addresses. We reverted back to our own in-house solution and I’m posting it here in hopes it will help anyone else experiencing the problem. There are a few requirements to get this working:

  • Forward syslog entries (filtered at your own discretion) from ISE to any syslog/syslog-ng server
  • Configure the necessary source, filter, and/or destination on your syslog server. This assumes you’re already listening for network sources.
  • Add your syslog server as a “Consumer Device” on all CDA servers
  • Install Python & PyRad on your syslog server
  • Configure the Python app, and associated modules, below:

Install PyRad first. I actually used a fork that had the correct support for cisc0-avpair. Next download the module used by the app being called from syslog-ng:

import random, socket, sys, logging
import pyrad.packet

from pyrad.client import Client
from pyrad.dictionary import Dictionary

logger = logging.getLogger(__name__)

#Logger Console Handler
ch = logging.StreamHandler() #StreamHandler logs to console
ch_format = logging.Formatter('%(asctime)s - %(message)s')

#Logger File Handler
fh = logging.FileHandler(".\{0}.log".format(__name__))
fh_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)-8s - %(message)s')

Now grab the program listening for syslog-ng $MESSAGE input. I’ve split the file into a configuration module (, and the actual program ( Modify to suite your environment:

import mod_radacct

# Dictionary path (can be relative or absolute):
dictionary = "dictionary"

# If missing a user domain in the syslog msg, this is provided:
default_domain = "DefaultDomain"

# List of CDA identity maintainers. Can include legacy AD Agent servers:
servers = [ "cda1", "cda2", "ada1", "ada2" ]

# RADIUS secret:
secret = "yourRadiusSecret"

#!/usr/bin/env python

import logging, re, sys, time
import mod_cda


logger = logging.getLogger(__name__)

#Logger Console Handler
ch = logging.StreamHandler()
ch_format = logging.Formatter('%(asctime)s - %(message)s')

#Logger File Handler; change to your desired path
fh = logging.FileHandler("/usr/local/cda/{0}.log".format("UpdateCDA"))

syslog-ng.conf (excerpt)

### Filter out ISE hosts that should be sending specific messages for Device/IP association:

filter f_ise_host 	{ 	(
				host("") or
				host("") or
				host("ise01") or

### The username and Framed-IP we're looking for are in the watchdog updates AFTER accounting "start" msgs:
filter f_ise_auth { match(".*RADIUS Accounting watchdog update.*" value ("MESSAGE")); };

destination d_NS_ISE { file("/var/log/syslog-ng/NS_logs/NS_ISE/$SOURCEIP/$SOURCEIP.log"); };

destination d_NS_pythonCDA 	{ 

log {	source(s_network);

Now you should be able to restart syslog-ng and see your application running (ps -ef | grep update_cda). It will continually listen for new messages and process them as needed. Feel free to change any of the configured logging levels to your preferred verbosity.

Powershell: Bulk provision DHCP Scopes

Thought I’d share a nice wrapper for netsh and dnscmd calls to allow easy, bulk provisioning of new DHCP scopes. It’s nice being able to provision a ton of these at once by piping the output from Import-CSV!


#AUTHOR:       JR Morgan
#CREATED:      20120417
#MODIFIED:     20140611

    Adds DHCP Scope to ALL specified DHCP servers. If split-scope is desired
	the script uses IP Math to automatically add the desired exlcude ranges.

    Creates a DHCP based on user-provided parameters. For 50/50 split-scope config,
	the ordering of the DHCP Servers determines the upper/lower designation. The first
	DHCP server specified will host upper scope portion, while the second DHCP server will
	host the lower scope portion. Upper/Lower 'tags' are added to the description when using
	the Split50 switch.

    .Parameter DhcpServer
        An array of DHCP Server IP names or addresses that will host the new DHCP scopes
    .Parameter IPScope
        The desired IP Scope

    .Parameter IPMask
        The desired IP mask for the scope

    .Parameter Description
        A brief scope description. Upper/Lower tags will
		automatically be added if using the Split50 switch
    .Parameter Gateway
        The IP address of the router or gateway for this scope (Option 3)
	.Parameter Dns
        An array of DNS server IP addresses utilized by scope clients (Option 6).
    .Parameter Domain
     	The fully-qualified domain name for scope clients (Option 15)

Check_MK: Local Checks Grab Bag!

Some local Check_MK checks that were created to execute check_MK local check scripts (Powershell) in 64-bit context, monitor Exchange 2007 health (Storage Group replication status, Log Truncation after backups, etc.), and monitor DNS scavenging on Windows servers:

@echo off
REM Note that SysNative is available on x86 2008, and on x86 2003 with KB942589 applied

CD %ProgramFiles(x86)%\check_mk\local-64
FOR /R %%X IN ("*") DO ( %WINDIR%\SysNative\windowspowershell\v1.0\powershell.exe -File "%%X")
$Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size(900,900)

Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin

$check_name = "Exchange_SG_LogTrunc"

$OK = 0
$Warn = 1
$Crit = 2
$Unk = 3
$Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size(900,900)

Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin

$check_name = "Exchange_SG_CopyStatus"

$OK = 0
$Warn = 1
$Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size(500,300)

$server = $env:COMPUTERNAME

$global:debug = ""
$global:n_stat = ""
$global:n_stattext = ""


One-person motorcycle loading

BG_Package (Medium)My wife is tiny… 5 ft tiny… Suffice it to say I needed a safe and easy way to load (and unload) my motorcycle without assistance. I researched the expensive solutions from LoadAll and deemed it unnecessarily complicated (with far too many moving parts) for my taste. I also didn’t want to sacrifice my truck cargo space. I decided on the 40″ wide, 8 ft. long Black Widow Ramp from combined with the Harbor Freight Motorcycle Stand/Wheel Chock (not permanently mounted — temporarily secured with ratchet straps). Although I’m only loading my 2007 GSXR600 (a whopping 354 lbs.) and occasionally my 2012 Hayabusa (still relatively light at 573 lbs.),  this ramp could easily handle my parents’ 2007 Harley FLHX Street Glide or 2012 Harley Sportster.

The Ramp

BG_Load (Medium)The 40″ wide, 8 ft. long Black Widow Ramp from is plenty long so the incline isn’t too steep, even when loading or unloading on a flat surface. It’s also wide enough to accommodate load-up mishaps requiring both feet down for immediate stabilization.

The Chock

Yes, Harbor Freight’s Motorcycle Stand/Wheel Chock is cheap and generic, but it gets the job done and I’ve never had an issue with it. I can’t say how it holds up to the elements since I remove it from my truck bed when not in use, but I’ve never had a doubt about its ability to hold my bike steady while it’s getting strapped-in for a ride.

I didn’t opt for a permanent mount solution. I couldn’t bring myself to drill through the truck bed. All of the motorcycle strap support should be pulling down and forward (towards the front of the truck and chock), so once the bike is IN the chock then a permanent mount isn’t really necessary. I also run a strap through the rear passenger peg supports (also pulling down and only slightly back). To secure the chock for unloading, I ran ratchet straps through its frame pulling it toward the front of the truck. This will keep things held down while you rock the bike out of the chock.

The Extra Mile

BG_Ready (Medium) If you still need some cargo room and some added security, consider a truck bed extender. I picked up a nice Amp Research Truck Bed Extender from Amazon. This gives me a little extra cargo space and some added security with regard to towing (peace of mind) and theft prevention. The extender can lock into your truck gate’s hardware, which can then be locked with a key. Sure, somebody could cut the straps holding the extender in place, but it’s one more step for a would-be thief.





Check_MK DFS Backlog Monitoring

Screenshot of inventoried replicated folders and their corresponding backlog counts
Inventoried replicated folders and their corresponding backlog counts

Before you even get started, make sure your Powershell execution policy is set to RemoteSigned for your standard AND x86 Powershell console; Check_MK will generally execute PS scripts from the x86 console, so it’s critical to set the policy for both:

  1. Start > Accessories > Windows PowerShell
  2. Right-click ‘Windows PowerShell (x86)’, select ‘Run As Administrator’
  3. Execute: Set-Execution Policy RemoteSigned
  4. Repeat the same steps above, but for the standard ‘Windows PowerShell’ console.

Next you’ll need to configure the Check_MK_Agent Service along with WMI and Component Services Security settings:

  1. Configure the ‘Check_MK_Agent’ service to utilize a domain user account (non-administrative). Make certain the ‘Log On As’ user is a member of a security group configured for WMI/COM access. Restart the service when modifying ‘Log On As’ account
  2. Each DFS Replicated folder has two or more Sending or Receiving members. These members can be determined by examining the replication group connections in the DFS management console.
  3. Perform the following security/access edits for all DFS Sending/Receiving members of a replication group (it will fail if you don’t do this for every replication member)
    1. Update WMI Security
      1. Start > Run > wmimgmt.msc
      2. Right-click ‘WMI Control’ > Select ‘Properties’
      3. Select ‘Security’ Tab
      4. Navigate to the proper ROOT\MicrosoftDfs namespace and click the ‘Security’ button
      5. Click ‘Adavanced’, Click ‘Add’ and enter the user or security group used for WMI access
      6. Select the following ‘Allow’ permissions:
        1. Execute Method
        2. Enable Account
        3. Remote Enable
        4. Read Security
    2. Update Component Services Security
      1. Start > Administrative Tools > Component Services
      2. From the Console Root, navigate to ‘Component Services’ > ‘Computers’ > ‘My Computer’
      3. Right-click ‘My Computer’,  select ‘Properties’
      4. Select the ‘COM Security’ tab.
      5. For both ‘Access Permissions’ and ‘Launch and Activation Permissions’, click ‘Edit Limits’ and add the user/group. ‘Allow’ all available permissions. Click OK and close all open windows.
  4. Verify you can successfully poll DFS replication group counts by running a Powershell terminal as the ‘Log On As’ account you specified for the check_mk_agent service. Execute the script below. If you’re receiving backlog counts for each and every RG connection then everything is configured and you’re ready to copy the PS script to the check_mk/local directory.
  5. An additional note: if you have a TON of replication groups (like I do), then I highly suggest downloading the Check_MK agent Innovation release and tweaking the local check timeout & cache settings. This will help, but likely not solve, issues with backlog check timeouts. An alternative is using a scheduled task for backlog counts, output to a status file, and use a simple ‘Get-Content’ in PS to output the status file contents when requested by check_mk

$computer = [System.Net.Dns]::GetHostName()
$Computer = [System.Net.Dns]::GetHostName()

# Fix issue with console text wrap:
$Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size (500, 300)

$OK = 0
$Warn = 1
$Crit = 2
$Unk = 3

#Warning/Critical Backlog [File] Counts:
$w_count = 350
$c_count = 700

[string]$RGName = ""
[string]$RFName = ""

$DebugPreference = "SilentlyContinue"
$ErrorActionPreference = "SilentlyContinue"

#region DFSQuery

### These thresholds are irrelevant for the local Check_MK thresholds (native to Steve Grinker's script):
[int]$WarningThreshold = 50
[int]$ErrorThreshold = 500

Function PingCheck

Extending Check_MK MySQL Status Output

If you need more output than what the existing Check of MySQL Status Variables includes, such as Seconds_Behind_Master or Relay_Log_Pos, consider adding a quick line to the bash call for mysql_status on the agent. This assumes you’ve installed themysql_status-1.0.1.mkp and deployed the necessary agent files to the servers you’d like to monitor. This also assumes you’ve correctly configured the credentials needed to run the plugin and successfully receive (standard) output from the agent plugin.

Continue reading Extending Check_MK MySQL Status Output

Powershell Script: Sync DHCP Reservations (Windows Server 2008)

If you have redundant Windows 2008 DHCP servers (likely with split-scope configurations), you’re familiar with the problem of keeping reservations between the servers synchronized. I figured I’d post a script I created to sync reservations between servers. Synchronization can be 1-to-1 or 1-to-many, depending on your redundant DHCP server configuration. This script can sync with ALL authorized DHCP servers in a domain if needed. Make sure to read the included Powershell help information. This doesn’t come with any warranty, but I’d be glad to answer any questions or consider suggestions for improvement. In my environment, this simply runs as a scheduled task on the secondary DHCP server.

Something to note: although it *should* work synchronizing to/from Windows Server 2003 servers, I had issues with remote netsh execution (noted in the  script comments below) from W7/2008 systems. I’ve commented out the offending section that handled 2003 DHCP servers since everyone should, realistically, be running a newer server OS. If there’s a major need to have 2003 DHCP reservation synchronization, ask and I might be able to spend some more time on the problem. I’ve since migrated from 2003 to 2008, so there wasn’t a need to handle these situations.

Synchronizes DHCP reservations to all or select authorized DHCP servers in a given domain.

This script utilizes a user-specified source (master) DHCP server and synchronizes
all or some scope reservations between other, authorized DHCP servers for a given domain.
Netsh commands can be generated for manual execution or executed automatically.

.Parameter Source
The source, authorized DHCP server providing the most current scope reservations.

.Parameter SyncAll
Perform synchronization with all authorized domain DHCP servers

.Parameter SyncSelect
Perform synchronization with user-specified authorized domain DHCP servers. Requires destinations.

.Parameter Destination
The destination, authorized DHCP servers where source reservations will configured.
Use 'All' switch to synchronize to all authorized DHCP servers in a given domain.

.Parameter Scoperanges
A comma separated list of scoperanges to include in synchronization. Used with -scope switch.
.Parameter Domain
The fully-qualified domain name for the domain hosting destination
DHCP servers (authorized).

.Parameter Domain
Specifies the domain; useful for multi-domain forests. Uses -match (regex)

.Parameter Purge
Purge reservations on all/destination DHCP servers that were removed from the source.

.Parameter Invoke
Invoke the netsh commands directly from this script. By default, this script will only
generate the required netsh commadns.

Created by JR Morgan, 20120409

-------------------------- EXAMPLE 1 --------------------------

Sync-Dhcp-Reservations -Source -SyncAll -Domain -Invoke N

This command generates commands (no invocation) to synchronize reservations between WinSource and WinDest DHCP servers.

-------------------------- EXAMPLE 2 --------------------------

Sync-Dhcp-Reservations -Source -Destination Server1,Server2,Server3 -Domain -Invoke N

This command generates commands (no invocation) to synchronize reservations between WinSource and an array of destination
DHCP servers. There's no limit to destination servers. This is useful if you do not want to synchronize a source to ALL
authorized DHCP servers for a given domain.

-------------------------- EXAMPLE 3 --------------------------

Sync-Dhcp-Reservations -Source -SyncAll -Domain -Scope-Invoke Y


ValueFromPipeline = $True,
ValueFromPipelineByPropertyName = $True,
Mandatory = $True,
HelpMessage="Specify a source DHCP to query for reservations")]
ParameterSetName = "DhcpAll",
ValueFromPipeline = $True,
ValueFromPipelineByPropertyName = $True,
HelpMessage="Syncs with all authorized DHCP servers.")]
ParameterSetName = "DhcpDest",
ValueFromPipeline = $True,
ValueFromPipelineByPropertyName = $True,
Mandatory = $True,
HelpMessage="Specify one or more destination DHCP server(s)")]
#ParameterSetName = "ScopeIncludes",
ValueFromPipeline = $True,
ValueFromPipelineByPropertyName = $True,
Mandatory = $False,
HelpMessage="Enter one or more scopes to be syncronized from source to destination server(s).")]
#ParameterSetName = "ScopeExcludes",
ValueFromPipeline = $True,
ValueFromPipelineByPropertyName = $True,
Mandatory = $False,
HelpMessage="Enter one or more scopes to be excluded from synchronization.")]
ValueFromPipeline = $True,
ValueFromPipelineByPropertyName = $True,
Mandatory = $True,
HelpMessage="Specify the domain hosting authorized DHCP servers you'd like sync")]
ValueFromPipeline = $True,
ValueFromPipelineByPropertyName = $True,
HelpMessage="Specify whether reservations deleted on the source should be removed from destination server(s).")]
ValueFromPipeline = $True,
ValueFromPipelineByPropertyName = $True,
HelpMessage="Specify whether the netsh commands should be invoked or only displayed")]
ValueFromPipelineByPropertyName = $True,
HelpMessage="Specify log file path (optional)")]

function Get-Dhcp-Servers()

$dhcps = netsh dhcp show server
$dhcps = $dhcps | ?{ $_ -match $Domain} | %{ $a = $_.Split(" "); $b = $a[1].replace("[",""); $b = $b.replace("]",""); $b}

return $dhcps

function Get-Dump ($server)

$header= @("proto","server","sip","scope","scoperange","action","opt","ip","mac","name","description","opt2")
$dhcpConfigs = @()

#Backup each server config before proceeding:
#Invoke-Expression "netsh dhcp server \\$server dump > $server-$(Get-Date -uformat "%d").dhcp"

#$dump = "$server-$(Get-Date -uformat "%d").dhcp"

#   $v = Get-WmiObject -ComputerName $server -Authentication PacketPrivacy -Impersonation Impersonate Win32_OperatingSystem

#There's a problem with remote netsh execution from W7/2008/newer to 2003/XP DHCP.
#Solved with a call to PsExec, but this condition is tested, then the presence of PsExec
#is tested.

$dump = Invoke-Expression "netsh dhcp server \\$server dump"

#    if ($v.version -gt 6)
#    {
#        $dump = Invoke-Expression "netsh dhcp server \\$server dump"
#    }
#    elseif (($v.version -lt 6) -and (Test-Path "PsExec.exe"))
#    {
#        $dump = Invoke-Expression "./PsExec.exe \\$server netsh dhcp server \\$server dump"
#        Start-Sleep -Seconds 20
#    }
#    else
#    {
#        throw "Problem determining destination DHCP server version for netsh command execution.`r`nYou may be missing PsExec.exe from the script directory."
#    }

$raw = $dump | Where {($_ -notmatch "#") -and ($_ -match "reservedip")} |
ForEach-Object { $_ }

if ($raw -ne $null)
#Import the content into a CSV array object:
$csvdump = $raw | ConvertFrom-Csv -Header $header -delimiter " "
throw "Problem retrieving configuration from $server.`r`nYou may not have appropriate permissions or you may be querying an older server version."

return $csvdump


function Compare-Dump ($sourcedump, $destdump)

Compare-Object -ReferenceObject $sourcedump -DifferenceObject $destdump -Property scoperange,action,opt,ip,mac -PassThru |
#?{ ($ -notmatch "unused")} |
Select -Property sip,name,scoperange,action,opt,ip,mac,@{Name="Configured on";Expression={if ($_.SideIndicator -eq "=>"){ "Destination" } elseif ($_.SideIndicator -eq "<="){ $source }}},description


function Set-ClearVars()
Remove-Variable [a..z]* -Scope Global
Remove-Variable [1..9]* -Scope Global

function Get-Destination-Sys()

$d = Get-Dhcp-Servers | ?{ ($_ -notmatch $source) -and ($_ -notmatch "gruprintpr01")}

if ($SyncAll)
return $d
elseif (($SyncSelect).count -ge 1)
foreach ($sys in $SyncSelect)
$dr += $d | ?{ $_ -match $sys }

return $dr

############## Main Block #####################

#Get source reservation config; this will be used frequently:
$sd = Get-Dump $source
$log = $null
$sourceIp = (Test-Connection -Count 1 $source).IPV4Address.IPAddressToString


$start = $(Get-Date -uformat "%Y%m%d%H%M%S")

$log += "(DEBUG) $start`r`n"
$log += "(DEBUG) User-provided source DHCP server:`t`t`t$source`r`n"
$log += "(DEBUG) User-provided domain name for DHCP sync:`t`t$Domain`r`n"
$log += "(DEBUG) User-provided SyncSelect server count:`t`t`t$(($SyncSelect).count)`r`n"
$log += "(DEBUG) User-provided SyncSelect server(s):`t`t`t$SyncSelect`r`n"
$log += "(DEBUG) Listing of all Dhcp-Servers detected for:`t`t$(Get-Dhcp-Servers)`r`n"
$log += "(DEBUG) Listing of desination server:`t`t`t`t$(Get-Destination-Sys)`r`n"

foreach ($s in Get-Destination-Sys)
$nsh_dis = @()
$nsh_add = @()
$c = $null
$cr = $null
$xs = $null

#Get destination server IP (for comparisons):
$destIp = (Test-Connection -Count 1 $s).IPV4Address.IPAddressToString

$log += "`r`n`r`n(DEBUG) ######## Syncing $s ######################################`r`n`r`n"

#Output for this should be null or empty if dhcp is synchronized:

$c = Compare-Dump $sd (Get-Dump $s)

#$log += "(DEBUG) Initial dump comparision for $source and $($s):`r`n"
$c | ft -auto

#Parse the dump info to include only relevant scopes:

if (($Scoperanges).count -ge 1)
$log += "(DEBUG) User-provided Scoperange count:`t`t`t`t$($Scoperanges.count)"
$log += "(DEBUG) User-provided Scopes to be synchronized:`t`t$Scoperanges"

foreach ($sc in $Scoperanges)
#Remove discrepancies for unspecified scoperanges (on source and dest servers):
$log += "(DEBUG) Filtering diff set by user-provided scope $sc"
$cr += $c | ?{$_.scoperange -eq $sc}

$log += "(DEBUG) Revised Scoperange-specific diffs:`r`n"
$cr | ft -auto
$c = $cr

$log += "(DEBUG) Diffs after reassignment to initial dump comp array:`r`n"
$c | ft -auto
$log += "(DEBUG) Count of diffs after scoperange specifics:`t`t`t$($c.count)`r`n"

if (($ExcludedScopes).count -ge 1)
foreach ($xs in $ExcludedScopes)
#Remove discrepancies for excluded scoperanges (on source and dest servers):
$log += "(DEBUG) Filtering diff set by user-provided excluded scope $xs"
"Removing $($c | ?{$_.scoperange -notmatch $xs})"
$c = $c | ?{$_.scoperange -notmatch $xs}

#$log += "(DEBUG) Revised Scoperange-exclusion diffs:`r`n"
#$cr | ft -auto
#$c = $cr

$log += "(DEBUG) Diffs after reassignment to initial dump comp array:`r`n"
$c | ft -auto
$log += "(DEBUG) Count of diffs after scoperange specifics:`t`t`t$($c.count)`r`n"

#Handle discrepancies for reservations that require removal/addition from destination servers:

foreach ($r in $c)
#If the serverIP/name matches the destination server, that means the records no longer exists or has changed on the
#source server; removed these reservations since we're syncing from a SINGLE master DHCP sys:
if (($r.sip -match $s) -or ($r.sip -eq $destIp))
$nsh_dis += "netsh dhcp server \\$s scope $($r.scoperange) delete reservedip $($r.ip) $($r.mac)`r`n"

#If the serverIP/name matches the source server (master), that means it's missing on the destination server;
#add these reservations since we're syncing to destination servers FROM the master.
if (($r.sip -match $source) -or ($r.sip -eq $sourceIP))
#$desc = ($r.mac + " " + (Convert-DNStoCN $Name))
$nsh_add += "netsh dhcp server \\$s scope $($r.scoperange) add reservedip $($r.ip) $($r.mac) `"$($`" `"$($r.description)`" BOTH`r`n"
$log += "`r`n(DEBUG) Reservations being removed from destination $s :`r`n"
$log += $nsh_dis
$nsh_dis | Out-File "netsh_cmd_dis_res.txt"
$log += "`r`n(DEBUG) Reservations being added to destination $s :`r`n"
$log += $nsh_add
$nsh_add | Out-File "netsh_cmd_add_res.txt"

if ($Invoke)
$log += "`r`n(DEBUG) Received Invoke Switch; Executing reservation operations on $s :`r`n"
foreach ($cm in $nsh_dis)
Invoke-Expression $cm -ErrorVariable err
foreach ($ca in $nsh_add)
Invoke-Expression $ca -ErrorVariable err

if ($err.count -ge 1)
$log += "Errors: `r`n$err `r`n"
$log += "No errors encountered; moving on`r`n"
$log += "`r`n(DEBUG) Finished invoking/syncing reservations to $s :`r`n"

#$nsh_add = $null
#$nsh_dis = $null

#Remove-Variable $nsh_add
#Remove-Variable $nsh_dis
$end = $(Get-Date -uformat "%Y%m%d%H%M%S")

$log += "`r`n`r`n(DEBUG) ######## $end - End of sync for $s ######################################`r`n`r`n"

Out-File -Encoding ASCII -InputObject $log -FilePath "dhcpSync.log" -Append

Nagios check_http problems with Microsoft System Center 2012 Endpoint Protection

Someone important suggested this get posted somewhere so that anyone else experiencing check_http socket timeout problems with client/servers running Microsoft System Center 2012 Endpoint Protection would have a clue as to what could be causing issues:

If you’re receiving strange/unexpected timeouts from the check_http plug-in when running it against a server using Microsoft System Center 2012 Endpoint Protection, the Network Inspection Service may be blocking your check attempt.

Don’t bother trying to force an agent string or any other weird options; either switch to header-checks-only [no body] using the -N option with check_http, or disable your Network Inspection service entirely (not recommended). I couldn’t locate granular settings for the Network Inspection service included with Endpoint protection 2012, though I saw references to promising settings included in 2010 [applied/managed via GPOs and custom ADMX Administrative Templates].

I haven’t had time to troubleshoot this problem further, but I’m likely to encounter it again when I need layer-7 string checks for hosts using Endpoint Protection 2012 with NIS enabled.