Use DcDiag with PowerShell to check domain controller health

One of the oldest and most useful tools to figure out what's going on in your Active Directory environment is dcdiag. Dcdiag is a command-line utility that comes with Windows. It allows administrators to run various diagnostic checks against their Active Directory environments.

In the earlier 2000s, a command-line utility that returned dumb strings would have been OK, but today we have smart objects with PowerShell! Let's build a wrapper around the dcdiag tool with PowerShell to bring this handy tool into the current times.

If you run dcdiag by itself, you can see that it returns some useful information, but this is just loose text. Dcdiag does not return objects we can parse and manipulate as we can in PowerShell.

Use DcDiag with PowerShell to check domain controller health

Use DcDiag with PowerShell to check domain controller health

To wrap this tool in PowerShell, we'll need to build a function. I'll call my function Invoke-DcDiag. We can get crazy with this, but to keep it simple, I want to pass a single domain controller (DC) to dcdiag, so I'll create this function with a parameter called DomainController.

Once I've built the skeleton, I can then add in the code. To run dcdiag against a domain controller, I need to use the /s switch, so I'll add that to my function. I need to capture the output, so I'll assign it to a variable. To mimic the code that will go into the function, I'll assign $DomainController a value of DC representing the name of the DC in my environment.

I've now captured the output to a variable. That's great and all, but we now need to parse the output. Let's get our regex on! Before we get that far though, I need to define what the output should look like. Eyeballing our sample output, it seems like for every test that happens, dcdiag always returns one of two strings:

I want PowerShell to return an object for each test result with a TestName, TestResult, and an EntityName to represent a domain controller, NT Directory Service (NTDS) partition, or whatever else the test is running against.

We'll need to parse each line in the string. One way to do this is to use Select-String. Select-String has a parameter called Pattern that allows us to try to match a string against a regular expression. Without teaching you regex, I'll give you the answer here. The regex you need for this is '\. (.*) \b(passed|failed)\b test (.*)'.

When you run it as is, you may think that either I'm crazy or it doesn't work. One of these things is right.

However, PowerShell is hiding regex groups from you. Each regex group contains each of the properties we'd like the resulting output to have. We reference these groups by looking at the various indexes in the Groups array that the Matches property contains that Select-String returns.

You can see below that I'm looping over each match, assigning each group value to an object property we'd like, and then returning the object.

Bringing this code all back into our function makes the function look like this:

At this time, we bring this function into our session, run it via Invoke-DcDiag -DomainController DC, and voila, dcdiag for the modern age!

Join the 4sysops PowerShell group!

Your question was not answered? Ask in the forum!

7+
avataravataravataravataravatar
Share
25 Comments
  1. Frank 1 year ago

    Hi,

    nice script!!1

    How can I put the whole Output to the Body of an email?

    Frank

    0

    • Hi Frank,

      there is no straight-forward way. Send-EmailMessage accepts only type String as Body and this is customobject. You would need to convert it to string, but that would loose the format and the output would be unreadable.

      Guess you need to create a HTML code from the output and pass it to Body parameter together with -BodyAsHTML switch.

      Leos

      1+
      avatar
    • @Frank

      You can try this:

      0

      • Hi Luc,

        I tried that, the output is a System.Collections.Hashtable and my output was something like this

        0

    • @Frank

      Leos is right, I forgot the Out-String cmdlet.
      Here is the fixed code, which I have tested.

      Thanks Leos for your eagle eye!

      2+
      avatar
      • Frank 1 year ago

        Hi all,

        thx for your anwers!

        I not want to use the whole function. Only sorted Output for:

        $result = dcdiag /s:$DomainController
        $result | select-string -pattern '\. (.*) \b(passed|failed)\b test (.*)' | foreach {
              $obj = @{
                Entity = $_.Matches.Groups[1].Value
                TestResult = $_.Matches.Groups[2].Value
                TestName = $_.Matches.Groups[3].Value
               }
              
         }
        $Body = [pscustomobject]$obj | Sort-Object -Property TestResult | ConvertTo-Html -Head $style | Out-String

        But i will not work.Ayn Idea?

        Frank

        0

        • Frank 1 year ago

          Got it right...;-)

          $result = dcdiag /s:$DomainController
          $result | select-string -pattern '\. (.*) \b(passed|failed)\b test (.*)' | foreach {
              $obj = "" | Select-Object TestResult,TestName,Target
              $obj.Target             = $_.Matches.Groups[1].Value
              $obj.TestResult     = $_.Matches.Groups[2].Value
              $obj.TestName     = $_.Matches.Groups[3].Value
              $Output += $obj 
              }

          $Body = $Output | Sort-Object -Property TestResult | ConvertTo-Html -Head $style | Out-String

          1+
          avatar
  2. Manlio 1 year ago

    Hi, can you help me to understand why I don't receive any output?

    after imported the function correctly, I run "Invoke-DcDiag -DomainController " but the output is empty.

    0

  3. Manlio 1 year ago

    It seems to be a problem with this part of the script select-string -pattern '\. (.*) \b(passed|failed)\b test (.*)' 
    because if I change the "-pattern" filter it works... 

    0

    • @Manlio

      What's the output when you run the command directly?

      0

      • Manlio 1 year ago

        @Luke Fullenwarth

         

        the output is correct as it should be , but the issue seems to be the "select-string" filter that's not working correctly, without that and applying another kind of "filter" it works.

         

        1+
        avatar
  4. Pascal 1 year ago

    Giving back, I added a few things

    • Parameter Validation (to ensure the DC is truly a DC)
    • By default the DCList is auto populated of every RWDCs
    • Run DCDiag with verbose output
    • Run DCDiag on DCs in parallel instead of sequential
    • Adds the full log as a by default hidden property

    0

  5. Vandrey Trindade 1 year ago

    Hi,

    My results are in portuguese. It isn't returning nothing, I have changed the "passed|failed" and still doesn't work.

    Here is a sample of my dcdiag command:

     

    0

    • @Vandrey

      You must replace english words with portugues words in the following command (line 9 of the function):

      Probably you have to replace:

      • passed
      • failed
      • test
      1+

      • Vandrey Trindade 1 year ago

        @Luc,

        Yup.

        I've tried that... but the problem is that in portuguese the "failed" translation is "não passou" (like 'not passed').

        So it breaks the script. But no problem! Thanks for helping!

        0

        • @Vandrey

          How does it brake the script? What's the error message?

          1+

          • Vandrey Trindade 1 year ago

            @Luc,

             

            Sorry for the delay... I got the dengue fever and I'm returning to work only today.

            There was no error, the problem was that since the translation to portuguese used two separated words, each word appeared on different columns.

            I'll try to run it on the server directly, it's on English language.

            Thanks!

            0

            • Vandrey Trindade 1 year ago

              Worked as expected directly on the server. Thanks!

              1+
              avatar
  6. Hi, I added this simple modification for use if running on DC
    $DC = (Get-WmiObject -Class Win32_ComputerSystem -Property Name).Name
    Invoke-DcDiag -DomainController $DC

    0

    • @Marcelo

      Yes, that works but is a bit overkill...

      There is an environment variable which stores the computer name.

      Usually, I set the ComputerName variable with the local computer name as the default value.

      Thus when you want to run a script or function on the local computer you don't need to specify the ComputerName parameter.

      0

  7. Manlio 1 year ago

    If use the last version by Pascal, it works fine!! yes

    thank you

    0

  8. damien 1 year ago

    Hi,

    Thanks for your help. In french it's à little different cause there are several line for test name + result

     Démarrage du test : Advertising
             ......................... Le test Advertising
              de DC a réussi
          Démarrage du test : FrsEvent
             ......................... Le test FrsEvent
              de DC a réussi

     

    How to catch it ?

    0

  9. damien 1 year ago

    That doesn't work cause in french result is on 2 lines and not in 1 line like english or portugese

    0

    • Understood. You would need some modification in regex expression. Will get back to you as I complete testing new regex in my lab.

      0

Leave a reply to Leos Marek (Rank: Level 4)
Click here to cancel the reply

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2020

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account