WD DX4000: Building Windows Server 2012, part 3 – the LCD

This is the third and final article in my series related to converting the DX4000 to Windows Server 2012. In this post, I cover some details related to using PowerShell to display performance information to the LCD. I created a video that demonstrates the script in action… take a look:

WMI Notes:

I make use of WMI Providers to hook event registers and capture metric data. This resulted in a nice asynchronous callback flow to the script and saved me not only the trouble of attempting to thread separate jobs, but also reducing CPU processing cycles. My first attempts to use the Start-Job and Get-WMIObject CmdLets ended up being huge failures with processor loads of up to 65% utilization and delays of nearly 12,000 milliseconds or more… in one metric capture. It was terrible. I found that these were just too in-efficient for what I wanted, a script to be able to cycle in ~1000ms or less.

I only use the common data collection providers in the script, but there are several available that can be used for your specific needs. TechNet has a list of those classes available (Win32 Classes), as well as a comprehensive description of the common counters (Performance Counter Notes). I was able to find an interesting resource related to the LCD micro controller that allowed me to eventually engineer the necessary steps to successfully program the LCD. You can learning more about this library (Inpoutx64.dll), but for the purpose of this article it isn’t necessary. I’ve spent quite a bit of time testing to get things working as simple as possible, so let’s get started.

Script Flow:

In Figure 1, I show a flow chart of the LCD script and its basic procedure steps. Nothing really special happening as it starts by initializing our variables, defines our functions, and starts a loop to process our event registers. That is the asynchronous part, we initiate the event registrations and instruct them to call “actions” when the events are raised while we continue to process our data and display the results. The data that these actions feed may not always be refreshed, as they are event based; however, for our use it is good enough and happens frequently enough to see reasonably accurate results.

Figure 1

Data Structure:

When looking through the code, there are a couple variables to note which are $counterOrder and $customBlock. The $counterOrder array contains the acronyms of the counters that I loop through in the main body of the script. If you want to add or remove items that get displayed to the LCD, this is where you can control it. The $customBlock is a byte array which holds a stream of values to define any custom characters we choose. The last 48 bytes in my configuration are, uh… zeros. I use this primarily to show that you can have up to 8 custom characters to use for your display; however, if you want to be able to have a dynamic graph you will need to reserve some space for it.


My original concept was to have the capability to read these byte streams in from a file, so that someone could have a large library of custom characters to choose from. Even though I was able to get that part working, I took the code out to lower the level of requirements to get things started. Now, this byte array is not what is used in the main code block to output our graph and custom characters to the LCD. This gets converted into a multidimensional array later called $blockArray, which makes the processing logic a little easier to control and it is used initially to pump the data onto the CGRAM stack.

 I create similar array types which are used for storing “background” metric states and for pumping data onto the CGRAM stack for the graph data. Their dimension is regulated by the variable $cells and is set to the value of the number LCD character spaces to use for the graph. I’ll refer to these as cells.


Oh, did I mention what the CGRAM stack is? Well, it is the programmable memory area of the LCD microcontroller. I discovered by incident that these memory ranges can be directly bound to what is called the DDRAM area, or the area that is actually seen on the display. When you output a character to the LCD, you are not actually doing anything but having the controller point to the memory range where that particular character is stored. Perhaps the controller is duplicating the data somehow in the background, but the action is performed nearly immediately. This is how I am able to get near immediate updates to the display in a single action by manipulating them on the CGRAM stack. Once the “memory range” or character has been bound to the DDRAM, you can write data directly into the CGRAM range and it will instantly update on the screen. Kind of cool huh?

There are two ways to view the CGRAM stack, as a linear stream of bytes (like the custom block array), or as a group of individual cells.  I prefer the second, visually it just makes sense to me and you will see in the functions, updateStoreArray() and updateGraph(), how I step through the cells row by row as individual columns.

Figure 2

There is one last thing I would like to mention related to some of the synchronous callbacks that are made in the script. You may notice, that sometimes I call a function to execute the PortWrite method, and other times I call it directly. Well, after a few days of performance testing, I discovered that in short cycle loops calling these often really increase processor load. So in the main loop and other functions I call the namespace directly instead of breaking out to a function and to another callback. PowerShell did not seem to like doing that very much.

LCD Layout:

    In Figure 3, I show an example of the LCD layout that I used to display the performance counters. You can use the function MoveCursorTo # #; to control where output goes on the panel. The top row is zero, while the second row is 1. You can think of the cells or columns being like an array from 0 to 15.

Figure 3

Task Scheduler:

There is a way to wrap PowerShell scripts in a C# executable and utilize them as services, I just did not have time to work on this. So, I took the easy route and simply created a task scheduler trigger to run the script at system startup. Place the script into your LCD directory where the 4 files from article 1 and 2 are stored. Open task scheduler and create a basic task, here are the properties I configured:

  • Run whether user is logged on or not
  • Triggers: At Startup
  • Actions:
    • Action = Start a Program
    • Details = C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
      • Arguments: -noexit -nologo -noninteractive -file “C:\LCD\lcd.ps1”
      • Start in: C:\LCD (remember that path? J )
  • Conditions: None
  • Settings: Allow task to be run on demand & Do not start a new instance

Figure 4

Error Handling:

There isn’t any… super scary! J

If you really need to put some in, I would recommend making sure to Try{}Catch{} for exceptions around the events and when PInvoking the unmanaged library. I did this when testing, I just did not need it for my purpose.


You must have the SIOUTIL.EXE, SIOAPI.DLL, and INPOUTX64.DLL files in the same library as the PowerShell script. Make sure you use the same path for the “start in” property of the task you create to keep things in context.


You can download the script from my SkyDrive at the following link:

LCD PowerShell Script

I hope you enjoyed the articles and video… If you have a question or find something odd in the script, please feel free to leave a comment.

Good luck out there!

Tagged with: , , , , ,
Posted in WD DX4000
35 comments on “WD DX4000: Building Windows Server 2012, part 3 – the LCD
  1. diehard says:

    Hi, I have WS2012E running on my DX4000. I also created a server OS backup to a external USB drive. I wanted to try a server recovery. I used the original WD restore software, but it does not see the USB drive with the backups. This must be due to WS2012E. Do you have any recommendations ?

    • tswalker says:

      I believe the WD restore software requires a remote computer running their “recovery” software and ISO. So if you made an image of the original windows partition, you can use DISM from the WinPE TightVNC build created in part 1 to restore the image. I don’t think their recovery USB would allow restoration this way…

  2. Paul Braren says:

    Incredible article, great work, fun to read, thank you!

  3. john says:

    powershell script works great!

  4. chrisccs says:

    Amazing set of posts, great detail, and representative of an incredible amount of work (both originally and with regard to the documentation)!

    Just wondering, why Intel NIC teaming instead of just using WS2012 NIC teaming feature?

    • tswalker says:

      great question and thanks! I honestly had not looked at it.. I guess it was instinctive to use the vendor features.. but glancing over the TechNet on it, I’ll have to investigate that. Initially, it seems that would be a good solution to use for non-compliant switches (which is my case anyway), but I do know with the Intel teaming, there are several types to choose from which includes switch fault tolerance and VM load balancing, plus some other types.

  5. what am I doing wrong that when the powershell runs it states “Unable to load DLL ‘inpoutx64.dll’: The specified module could not be found. (Exception from HRESULT: 0x8007007E)”

    • tswalker says:

      make sure that you have those files from the original WD installation that I mentioned in part 2 included in the same directory as the powershell script:

      I can not provide them, they are Western Digitals and came with your DX4000. You can also extract them from the recovery ISO if needed (I do not outline the steps to do that though).

      If you have tested them as mentioned at the end of Part 2 to “clear” the LCD and display a message (in the area stated “What about the LCD?”) and it works fine, however it is failing with the script.. you may need to include the path to them and the script in your environment settings. To do this, go into “advanced system settings” > advanced > environment variables

      under the section “system variables”, add a new item with variable name LCD and variable value C:\LCD (or whatever the path is to the directory you placed those files and the powershell script).
      located the Path system variable in the scroll window, select edit, and at the end of the variable value add ;C:\LCD

      click ok, ok, ok and try executing the script again. You can do this from the command line to make sure it is working before setting up the scheduler.

  6. azwald says:

    Hi, after you upgrade the OS to 2012, is the auto-RAID configuration still working?
    2 HDD->RAID 1
    >3HDD -> RAID 5
    or can we change the RAID setting ourself?

    • tswalker says:

      Hi azwald, although I have not tested it… I am pretty confident that it is built into the EFI (bios) for the controller config and may auto-configure based on the number of drives installed. However, I can not say for certain this is the case as I have not tested it. The way to know for sure, is to build the system with one disk, get it running.. shut it down and plug another in and see if it auto builds. I purchased mine with all 4 drives already in RAID 5, and just didn’t have time to do these tests. Would be interesting to know though, and I’m sure others would be too. I just think, that based on Western Digitals documentation, that these modes are setup in EFI … if we were able to get our hands on a remote EFI tool for this system, I feel confident we could modify how it sets it up. ( I just simply do not know).

  7. Ryan says:

    Hi there – NICE GUIDE! Just was curious if you knew how to get the power light to stay on instead of blink. With the old WSSE 2008 R2 stock setup, it would stop blinking when the system finished booting, but (of course) now there is nothing telling the light that the system is completely booted into the OS. Is there a command for the SIOUTIL or something that can switch from blinking to on?

    • tswalker says:

      Hi Ryan, and thanks. Unfortunately, I have not been able to figure out the right codes to deal with the lights… I believe that since the LCD controller and the lights are on the same PCB, that the LPC controller can (or may) also be able to control them. However, after spending a few days (literally), trying to figure out the right sequence of codes to send.. I had to move on and never completed that part. There is a document somewhere out there from Nuvoton about the NCT6776F LPC I/O controller regarding the Port 80 Message Display and LED Control. I just couldn’t get the sequence right. Perhaps someone from WD could throw a guy a bone 🙂

      • Cause I’m dragging this thread up… Some hex editing in lpc.exe revealed sys0 sata0 sata1 sata2 and sata3. So, you can issue “lpc sys0 primary” to make it red, sys0 secondary for blue, and sys0 blinkprimary or blinksecondary to make it blink. I’m editing that into my LCD script that sets the fan speed and sets up the lcd info output.

      • Well, not sure if my last comment was eaten, or what, but I discovered that you can use lpc.exe to set the leds, but apparently you cannot pass the exe the variable, you have to enter it manually. led names are sys0 sata0 sata1 sata2 sata3 sata4 and options are primary (red) secondary(blue) blinkprimary blinksecondary and off.

      • tswalker says:

        I’ve been out on contract and just noticed your post… That’s why 😃.

        This is awesome information you’ve dug out! Many thanks!

        When I get back to normal life, I’ll look at updating this.

      • So I edited the .ps1 to simply call a batch file in the area where it used to set the fan speed, and now it just runs:

        #$fanSpeed = &’./sioutil.exe’ ‘setfan’ ‘sysfan’ ’25’;
        $fanSpeed = &’./setfanandlight.bat’;

        and the bat…
        sioutil.exe setfan sysfan 25
        echo sys0 secondary |lpc

      • mostly because I’ve figured out that you cant pass the exe variables, I had to pipe them in.

      • tswalker says:

        This is interesting though because it says that through LPC, we may be able to P/Invoke with powershell to the controller…

  8. Jason says:

    Hey- I don’t suppose you could modify the LCD script to include the IP assigned to the team/NIC. That was a handy feature on the factory install.

    • tswalker says:

      In the script, there is a section that queries/posts the IP address (early on, like around line #50), you should be able to adapt the script to incorporate that between graph cycles…

  9. IN says:

    Do you know if the DX4000 saves status of events such as door open, software update available, drive status, RAID status to WIM? If so do you know the locations?

    • tswalker says:

      Hi IN,

      There’s no support for detecting if the case or door have been open… you can find this on some systems, but not this one. There is a mechanism to lock the drives, but I don’t recall how it is activated.

      WMI, perhaps some of those features are available… I just don’t know, you’d need to give WMICodeCreator a try, or some other WMI browser to check. I do know (for example), that Open Hardware Monitor adds some WMI objects to monitor temperature and fan speed. Intel does this for their products too.

  10. IN says:

    Thank you for your help.

  11. Ingo Böhm says:

    Hi folks, im almost done, i followed this awesome guide but now i’m stuck at the last mile. I’m trying to get the LCD-Script up and running. I have the necessary files: inpoutx64.dll / lcd.exe / lcd.ps1 / sioutil.exe / sioapi.dll located under c:\lcd i also added ;c:\lcd to the environment variable Path but the script keeps failing. Below are the errors thrown by the script:

    PS C:\LCD> lcd.ps1

    Security warning
    Run only scripts that you trust. While scripts from the internet can be useful, this script can potentially harm your
    computer. If you trust this script, use the Unblock-File cmdlet to allow the script to run without this warning
    message. Do you want to run c:\lcd\lcd.ps1?
    [D] Do not run [R] Run once [S] Suspend [?] Help (default is “D”): r
    Method invocation failed because [System.Char] does not contain a method named ‘Replace’.
    At c:\lcd\lcd.ps1:386 char:5
    + $iTemp1 = [System.Convert]::ToInt16(($dts0[4].Replace(“IntDTS:”,””)));
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

    Method invocation failed because [System.Char] does not contain a method named ‘Replace’.
    At c:\lcd\lcd.ps1:388 char:5
    + $iTemp2 = [System.Convert]::ToInt16(($dts1[4].Replace(“IntDTS:”,””)));
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

    You cannot call a method on a null-valued expression.
    At c:\lcd\lcd.ps1:390 char:5
    + OutPutArray($iTemp1.ToString(“G2”));
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    You cannot call a method on a null-valued expression.
    At c:\lcd\lcd.ps1:392 char:5
    + OutPutArray($iTemp2.ToString(“G2”));
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    Exception calling “ToInt16” with “1” argument(s): “Input string was not in a correct format.”
    At c:\lcd\lcd.ps1:688 char:5
    + $buttonDown = [System.Convert]::ToInt16((&’./sioutil.exe’ ‘get’ ‘btn1′).Repl …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : FormatException

    Exception calling “ToInt16” with “1” argument(s): “Input string was not in a correct format.”
    At c:\lcd\lcd.ps1:689 char:5
    + $buttonUp = [System.Convert]::ToInt16((&’./sioutil.exe’ ‘get’ ‘btn2’).Replac …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : FormatException

    PS C:\LCD>
    Any Idea whats wrong?

    Cheers Ingo

    • tswalker says:

      I’m not sure what the exact problem would be, but I would first start by updating powershell and set the execution policy for it to allow running scripts. There should be some guides for that on Microsoft support. You may also try opening the script on the server using Powershell IDE, and seeing if it runs ok from there.

  12. Ingo Böhm says:

    Hi, thanks for the fast reply, i changed the execution policy for powershell but the script keept failing. The “fix” was installing the lpcdriver.msi file from the DX4000s recovery media set. After that followed by a reboot everything worked like a charm. I really must say you did an incredible job on that. Thanks a lot!

  13. Mike says:

    The SkyDrive link for the LCD PowerShell Download is offline 😦
    Can you please reupload the script?

  14. Mike says:

    Works like a charm in IE or Chrome. My Firefox apparently had a problem with the download.
    Thanks for your reply.

  15. RHDotmatrix says:

    Is it possible to get a copy of that LCD script, the link doesn’t seem to be working any more


  16. Dipak Raste says:

    Unable to download http://sdrv.ms/15Unlcc

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: