'Script to check the status of a DOMAIN controller and report to Nagios
'requires DCDIAG.EXE 
'Author: Felipe Ferreira
'Version: 3.0
'
'Mauled over by John Jore, j-o-h-n-a-t-j-o-r-e-d-o-t-n-o 16/11/2010 to work on W2K8, x32
'as well as remove some, eh... un-needed lines of code, general optimization as well as adding command parameter support
'This is where i found the original script, http://felipeferreira.net/?p=315&cpage=1#comments
'Tested by JJ on W2K8 SP2, x86
'		 W2K3 R2 SP2, x64
'Version 3.0-JJ-V0.2
'Todo: Proper error handling
'      Add /help parameter
'      Add support for the two tests which require additional input (dcpromo is one such test)
'Version 3.0-JJ-V0.3
'	Removed some surplus language detection code
'		Including non-working English test on a W2K8 x32 DC
'	Added support for multi-partition checks like 'crossrefvalidation'. Previously the last status result would mask previous failures
'	Incorporated Jonathan Vogt's german and multiline tests
'Version 3.0-DAE-V0.4
'	Added support for DNS test parameters (dns /dnsbasic /dnsdelegation)
'	Fix problem to alert status NotOk


'Force all variables to be declared before usage
option explicit

'Array for name and status (Ugly, but redim only works on last dimension, and can't set initial size if redim 
dim name(), status()
redim preserve name(0)
redim preserve status(0)
redim preserve lock(0)

'Debug switch
dim verbose : verbose = 0

'Return variables for NAGIOS
const intOK = 0
const intWarning = 1 'Not used. What dcdiag test would be warning instead of critical?
const intCritical = 2
const intUnknown = 3

'Lang dependend. Default is english
dim strOK : strOK = "passed"
dim strNotOK : strNotOk = "failed"

'Call dcdiag and grab relevant output
exec(cmd)

'Generate NAGIOS compatible output from dcdiag
printout()

'call dcdiag and parse the output
sub exec(strCmd)
	'Declare variables
	dim objShell : Set objShell = WScript.CreateObject("WScript.Shell")
	dim objExecObject, lineout, tmpline
	lineout = ""
	'Command line options we're using
	pt strCmd 

	Set objExecObject = objShell.Exec(strCmd)
	'Loop until end of output from dcdiag
	do While not objExecObject.StdOut.AtEndOfStream
		tmpline = lcase(objExecObject.StdOut.ReadLine())

		'Check the version of DCDiag being used and change the global 'passed' / 'failed' strings
		call DetectLang(tmpline)

              	if (instr(tmpline, ".....")) then 
			'testresults start with a couple of dots, reset the lineout buffer
			lineout= tmpline
			'pt "lineout buffer '" & lineout & "'"
		else
			'Else append the next line to the buffer to capture multiline responses
			lineout = lineout + tmpline
			'pt "lineout buffer appended '" & lineout & "'"
		end if

		if instr(lineout, lcase(strOK) & " test") OR instr(lineout, lcase(strNotOK) & " test") then
			'we have a strOK String which means we have reached the end of a result output (maybe on newline)
			call parse(lineout)
			lineout = ""
		end if 
	loop

	'Why call this at the end? Is it not pointless as we've looped through the entire output from dcdiag in the above loop?!?
	'call parse(lineout)
end sub


sub DetectLang(txtp)

	'Change from looking for English words if we find the string below:
	if (instr(lcase(txtp), lcase("Verzeichnisserverdiagnose"))) then 'German
                pt "Detected German Language, changing the global test strings to look for"
		strOK = "bestanden"
		strNotOk = "nicht bestanden"
	end if

end sub


sub parse(txtp)

	'Parse output of dcdiag command and change state of checks
	dim loop1
	dim strname

        'Is this really required? Or is it for pretty debug output only?
	txtp = Replace(txtp,chr(10),"") ' Newline
	txtp = Replace(txtp,chr(13),"") ' CR
	txtp = Replace(txtp,chr(9),"")  ' Tab
	do while instr(txtp, "  ")
	  txtp = Replace(txtp,"  "," ") ' Some tidy up
	loop

	' We have to test twice because some localized (e.g. German) outputs simply use 'not', or 'nicht' as a prefix instead of 'passed' / 'failed'

	for loop1 = 0 to Ubound(name)-1

		if instr(lcase(txtp), lcase(strOK)) then
			'What are we testing for now?
			pt "Checking :" & txtp & "' as it contains '" & strOK & "'"
			'What services are ok? 'By using instr we don't need to strip down text, remove vbCr, VbLf, or get the hostname

			for each strname in split(lcase(name(loop1)))

				if (instr(lcase(txtp), strname)) AND (lock(loop1) = FALSE) then 
					status(loop1)="OK"
					pt "Set the status for test '" & name(loop1) & "' to '" & status(loop1) & "'"
				end if

			next
		end if

		' if we find the strNotOK string then reset to CRITICAL
		if instr(lcase(txtp), lcase(strNotOK)) then
			'What are we testing for now?
			pt txtp
			for each strname in split(lcase(name(loop1)))
				if (instr(lcase(txtp), strname)) then 
					status(loop1)="CRITICAL"
					'Lock the variable so it can't be reset back to success. Required for multi-partition tests like 'crossrefvalidation'
					lock(loop1)=TRUE
					pt "Reset the status for test '" & name(loop1) & "' to '" & status(loop1) & "' with a lock '" & lock(loop1) & "'"
				end if
			next
		end if

	next

end sub

'outputs result for NAGIOS
sub printout()
	dim loop1, msg : msg = ""

	for loop1 = 0 to ubound(name)-1
		msg = msg & name(loop1) & ": " & status(loop1) & ". "
	next

	'What state are we in? Show and then quit with NAGIOS compatible exit code
	if instr(msg,"CRITICAL") then
		wscript.echo "CRITICAL - " & msg
		wscript.quit(intCritical)
	else
		wscript.echo "OK - " & msg
		wscript.quit(intOK)
	end if
end sub

'Print messages to screen for debug purposes
sub pt(msgTxt)
	if verbose then
		wscript.echo msgTXT
	end if
end sub

'What tests do we run?
function cmd()
	dim loop1, test, tests
	const intDefaultTests = 7

	cmd = "dcdiag " 'Start with this

	'If no command line parameters, then go with these defaults
	if Wscript.Arguments.Count = 0 Then
		redim preserve name(intDefaultTests)
		redim preserve status(intDefaultTests)
		redim preserve lock(intDefaultTests)
		'Test name
		name(0) = "services"
		name(1) = "replications"
		name(2) = "advertising"
		name(3) = "fsmocheck"
		name(4) = "ridmanager"
		name(5) = "machineaccount"
		name(6) = "dns /dnsbasic"

		'Set default status for each named test
		for loop1 = 0 to (ubound(name)-1)
			status(loop1) = "CRITICAL"
			lock(loop1) = FALSE
			cmd = cmd & "/test:" & name(loop1) & " "
		next
	else
		'User specified which tests to perform.

		for loop1 = 0 to wscript.arguments.count - 1
			if (instr(lcase(wscript.Arguments(loop1)), lcase("/test"))) then
			
			'If parameter is wrong, give some hints
			if len(wscript.arguments(loop1)) < 6 then
				wscript.echo "Unknown parameter. Provide name of tests to perform like this:"
				wscript.echo vbTAB & "'cscript //nologo " & Wscript.ScriptName & " /test:advertising,dfsevent'"
				wscript.quit(intUnknown)
			end if
			
			'Strip down the test to individual items
			tests = right(wscript.arguments(loop1), len(wscript.arguments(loop1))-6)
			pt "Tests: '" & tests & "'"

			tests = split(tests,",")
			for each test in tests

				cmd = cmd  & " /test:" & test

				'Expand the array to make room for one more test
				redim preserve name(ubound(name)+1)
				redim preserve status(ubound(status)+1)
				redim preserve lock(ubound(lock)+1)

				'Store name of test and status
				name(Ubound(name)-1) = test
				status(Ubound(status)-1) = "CRITICAL" 'Default status. Change to OK if test is ok
				lock(Ubound(lock)-1) = FALSE 'Don't lock the variable yet.

				'pt "Contents: " & name(Ubound(name)-1) & " " & status(Ubound(status)-1)
			next
			end if
		next
	end if
	'We end up with this to test:
	pt "Command to run: " & cmd
end function