WES: Changing System Sounds from the Command Line


This article will discuss how to disable the system sounds via the registry.  I know that the title is change system sounds, but trust me if you can disable the sounds, you can also change them.  For this article, I will change the registry using a batch file.

My customer requirements are:

  1. Disable system sounds
  2. Disable the start up sound 

Once I disabled the sounds, the customer changed the requirement to include setting the sounds scheme, so we will do that also.

Starting with disabling the system sounds, we first need to know that the system sounds are set in the registry in subkeys of HKEY_CURRENT_USER\AppEvents\Schemes\Apps.  The current sound for each item is defined in the default value for a key named “.current”.   So, to change the sound played for any item, we simply need to change the string value stored in the default value for the key named “.current”, further to disable the sound we simply set the value to the empty string (“”).   Since my requirement is to disable all of the system sounds, I need to search for all subkeys named “.current”.

The first thing to do is look for all subkeys of HKEY_CURRENT_USER\AppEvents\Schemes\Apps.   To do that, use reg.exe and query recursively for all subkeys:

    reg query HKCU\AppEvents\Schemes\Apps /s > AppsKeys.txt

which outputs the results to a file that we can process.  To process the subkeys, I will use the following function:

:DisableSystemSounds
    set KeyFile=AppsKeys.txt
    reg query HKCU\AppEvents\Schemes\Apps /s > "%KeyFile%"

    FOR /F %%I in (%KeyFile%) DO (
        if NOT "%%I"=="(Default)"  call :ProcessFoundData "%%I"
    )
    del "%KeyFile%"
 
    goto :EOF

If you look at the output, you will see that it includes both the keys and values.   Luckily for this batch file, the values are all named “(Default)”.  So with that information, I simply exclude all keys named (Default) and process everything else with the ProcessFoundData function:

:ProcessFoundData
    set Key=%~1
    call :KeyExists "%key%\.current"
    IF not ERRORLEVEL 1 (
        reg add "%key%\.current" /t REG_SZ /d "" /f 2>nul >nul
    )

    goto :EOF

ProcessFoundData looks for keys that contain a subkey named “.current” and sets the default value to the empty string.  Simple enough, but it took me several tries to figure out how to determine if the subkey “.current” exists, and the result is the KeyExists function below:

:KeyExists
    REM if the key exists, ERRORLEVEL will be zero
    reg query "%~1" /ve 2>nul >null
    goto :EOF

So, now I have disabled the system sounds, but it turns out that the Windows startup sound is handled differently.  The Windows startup sound can both be disabled and the user can be prevented from changing the setting in the Control Panel.  The following uses reg.exe to turn off the Windows startup sound and disable the user fro changing the setting:

REM  Disable user from enabling the startup sound
reg add HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v DisableStartupSound /t REG_DWORD /d 1 /f 2>nul >nul

REM Somehow setting DisableStartupSOund here actually disables the sound (kind of opposite...)
reg add HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\BootAnimation /v DisableStartupSound /t REG_DWORD /d 0 /f 2>nul >nul

 

And finally, change the sound scheme to No Sound:

REM Set the sound scheme to No Sound
reg add HKCU\AppEvents\Schemes /t REG_SZ /d ".None" /f 2>nul >nul

To be honest, that isn’t really setting the scheme to No Sound, just the name that is displayed.   If you really want to change the sound scheme the way the Control Panel does, I suspect that you will need to copy the default values from the .None sounds to the .current settings.

Putting this all together, the batch file is:

@echo off


REM  Disable user from enabling the startup sound
reg add HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v DisableStartupSound /t REG_DWORD /d 1 /f 2>nul >nul

REM Somehow setting DisableStartupSOund here actually disables the sound (kind of opposite...)
reg add HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\BootAnimation /v DisableStartupSound /t REG_DWORD /d 0 /f 2>nul >nul

REM Set the sound scheme to No Sound
reg add HKCU\AppEvents\Schemes /t REG_SZ /d ".None" /f 2>nul >nul


call :DisableSystemSounds


goto :EOF

:DisableSystemSounds
    set KeyFile=AppsKeys.txt
    reg query "HKCU\AppEvents\Schemes\%1" /s > "%KeyFile%"

    FOR /F %%I in (%KeyFile%) DO (
        if NOT "%%I"=="(Default)"  call :ProcessFoundData "%%I"
    )
    del "%KeyFile%"
 
    goto :EOF

:ProcessFoundData
    set Key=%~1
    call :KeyExists "%key%\.current"
    IF ERRORLEVEL 1 (
        echo not found > nul
    ) else (
        rem echo XX %key%\.current
        reg add "%key%\.current" /t REG_SZ /d "" /f 2>nul >nul
        echo .
    )

    goto :EOF

:KeyExists
    REM if the key exists, ERRORLEVEL will be zero
    reg query "%~1" /ve 2>nul >null
    goto :EOF

author: Bruce Eitman | Posted On Tuesday, July 15, 2014 10:44 PM | Comments (0)

WES: Rotate Display


As part of my continues set of articles about setting up WES, the following shows how to rotate the display from a batch file – don’t get too excited, first we will need to write an application to do it.  The reason that I want to do this from a batch file is that in the end, I want a set of batch files that will install drivers, like the display driver, and do some configuration of the system.   So this is just one step on my adventure.

Note that this Article originally will target Windows 7.  Given time, I will try it on other versions.

I started by looking at the code that I had for Windows CE (see Windows CE: Dynamic Screen Rotation) which was close, but not close enough.

So starting over.   I decided to ignore the recurring note that use of ChangeDisplaySettings() is incompatible on Windows 7 and give it a try.  To keep a long story short, I couldn’t find a reason not to use it.  It may be that the incompatibility is with other features of ChangeDisplaySettings().

My first attempt was simple, get the current settings using EnumDisplaySettings(), change the orientation, and call ChangeDisplaySettings().  That seemed promising at first, I could rotate to 180 degrees and back to 0, but it failed to go to 90 and 270.   This was simple enough to fix though, when rotating, the width and height must be set correctly.

This new version called ChangeDisplaySettings() passing the DEVMODE structure and zero.  If  you give that a try, you will see that it works to dynamically rotate the display.   The problem is that on reboot, the system went back to the original orientation.   Again, a simple fix.   The second parameter can be used to tell the system to save the settings to the registry for future use.

I kept this code simple, because I plan to control its use via batch files.  You may want to do more complex handling of command line arguments.

The following is the entire code.

#include "stdafx.h"
#include <windows.h>

bool RotateDisplay( int angle )
{
    DEVMODE dm;
    long lRet;
    bool RetVal = false;
    bool SwapHW = false;

    // initialize the DEVMODE structure
    ZeroMemory(&dm, sizeof(dm));
    dm.dmSize = sizeof(dm);

    if ( 0 != EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm))
    {
        // determine new orientaion
        switch (angle)
        {
            case 270:
                if( dm.dmDisplayOrientation == DMDO_180 || dm.dmDisplayOrientation == DMDO_DEFAULT )
                    SwapHW = true;
                dm.dmDisplayOrientation = DMDO_270;
                break;
            case 180:
                if( dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270 )
                    SwapHW = true;
                dm.dmDisplayOrientation = DMDO_180;
                break;
            case 90:
                if( dm.dmDisplayOrientation == DMDO_180 || dm.dmDisplayOrientation == DMDO_DEFAULT )
                    SwapHW = true;
                dm.dmDisplayOrientation = DMDO_90;
                break;
            case 0:
                if( dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270 )
                    SwapHW = true;
                dm.dmDisplayOrientation = DMDO_DEFAULT;
                break;
            default:
                return false;
        }
       
        if( SwapHW )
        {
            // swap height and width
            DWORD dwTemp = dm.dmPelsHeight;
            dm.dmPelsHeight= dm.dmPelsWidth;
            dm.dmPelsWidth = dwTemp;
        }
       
        // Rotate the display
        lRet = ChangeDisplaySettings(&dm, CDS_GLOBAL | CDS_UPDATEREGISTRY );
        if (DISP_CHANGE_SUCCESSFUL == lRet)
        {
            RetVal = true;
        }
    }

    return RetVal;
}


int _tmain(int argc, _TCHAR* argv[])
{
    printf( "DisplaySettings\r\n");
    if( argc > 1 )
    {
        int angle = -1;
        if( wcscmp( argv[1], L"90" ) == 0 )
            angle=90;
        else if( wcscmp( argv[1], L"180" ) == 0 )
            angle=180;
        else if( wcscmp( argv[1], L"270" ) == 0 )
            angle=270;
        else if( wcscmp( argv[1], L"0" ) == 0 )
            angle=0;

        if( angle != -1 )
            RotateDisplay( angle );
    }
    return 0;
}

author: Bruce Eitman | Posted On Monday, July 14, 2014 1:41 PM | Comments (0)

WES: Changing Power Settings from the Command Line


I am working on a kiosk type system right now – really, it is a kiosk.   So we want the user to feel like the system is running all of the time, and the power settings to turn off the display hinder that.

The settings are readily available in the control panel, but that isn't very convenient.   I want to control them from batch file which would help remove the human error problem.   My first attempt was to start the power control panel from the command line – at least this reminded me that I needed to change the settings…    To start the power control panel applet, run “control /name microsoft.poweroptions”.

But it would be much better if I could change the settings.   They must be in the registry, but I wasn't able to figure those out (see note at bottom).   Better though is the use of powercfg.exe which allows the settings to be changed from the command line.

Running “powercfg /?” will show the many command line options, but I only need to change settings for the current power scheme.   So running “powercfg /? Change” will show that the following are configurable:

  • ·        monitor-timeout-ac
  • ·        monitor-timeout-dc
  • ·        disk-timeout-ac
  • ·        disk-timeout-dc
  • ·        standby-timeout-ac
  • ·        standby-timeout-dc
  • ·        hibernate-timeout-ac
  • ·        hibernate-timeout-dc

I want to disable these which can be done by setting the timeout to zero (0):

powercfg /change monitor-timeout-ac 0

 

Doing that over and over in a batch file seems cumbersome, so let’s read a list from a file and change the settings:

@echo off
set PowerListFile=powerlist.txt

FOR /F %%i in (%PowerListFile%) DO (
               call :DisablePowerSetting %%i
)
goto :EOF

:DisablePowerSetting
               powercfg /change %1 0
goto :EOF

 

And powerlist.txt might contain:

monitor-timeout-ac
monitor-timeout-dc
disk-timeout-ac
disk-timeout-dc
standby-timeout-ac
standby-timeout-dc
hibernate-timeout-ac
hibernate-timeout-dc

NOTE: I was able to find a few registry settings for values not controlled by powercfg.  These are:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\PowerSettings\7516b95f-f776-4464-8c53-06167f40cc99\17aaa29b-8b43-4b94-aafe-35f64daaf1ee\DefaultPowerSchemeValues\381b4222-f694-41f0-9685-ff5bb260df2e]
     "DcSettingIndex"=dword:00000000
     "AcSettingIndex"=dword:00000000

 

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\PowerSettings\7516b95f-f776-4464-8c53-06167f40cc99\17aaa29b-8b43-4b94-aafe-35f64daaf1ee\DefaultPowerSchemeValues\8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c]
     "DcSettingIndex"=dword:00000000
     "AcSettingIndex"=dword:00000000

 

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\PowerSettings\7516b95f-f776-4464-8c53-06167f40cc99\17aaa29b-8b43-4b94-aafe-35f64daaf1ee\DefaultPowerSchemeValues\a1841308-3541-4fab-bc81-f71556f20b4a]
     "DcSettingIndex"=dword:00000000
     "AcSettingIndex"=dword:00000000

The GUIDs make this very messy.   But, on my system, these control the Balance power scheme settings for Sleep, Hard Disk Shutdown and a second setting for turning off the display. 

author: Bruce Eitman | Posted On Tuesday, June 24, 2014 2:52 PM | Comments (0)

WES: Disabling the Taskbar


There has been plenty written about replacing the Explorer Shell to disable the taskbar, but what if you want the Explorer Shell, but don’t want the taskbar?  This article will show how to disable the taskbar at runtime from your application.

I have tested this on Windows XP and Windows 7, both embedded and non-embedded.

So there are some things that we need to do, and some that are optional:

1.      Hide the taskbar.   This just makes it less visible while the system boots before you disable it from your application.

2.      Unlock the taskbar, which allows it to be hidden and disabled.

3.      Disable hot keys.   While the taskbar can be disabled, there are problems, like the Windows key will show the Start menu in Windows 7.  So if we disable some taskbar related keys we can avoid the problems.

4.      Disable the taskbar.  From an application, hide and disable the taskbar.

 

First, hide the taskbar.  I did a lot of research on this one and found that everybody who knew said something about changing Settings in HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\StuckRects2 in the registry.   What I found was that this registry value is written to the registry when Explorer shuts down.  So, any changes that you need to make must be made either using Explorer or while Explorer is shut down.

StuckRects2 is a REG_BINARY value, which means that it is a long set of hex values.   Through some research and some trial and error, I found that the 9th byte effects the taskbar auto hide feature.   A value of 02 disables auto hide, and a value of 03 enables auto hide.

So for me, the following enabled auto hide:

[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\StuckRects2]
     "Settings"=hex:28,00,00,00,ff,ff,ff,ff,03,03,00,00,03,00,00,00,3e,00,00,00,28,\
      00,00,00,00,00,00,00,d8,03,00,00,00,03,00,00,00,04,00,00

Because all of the other values probably do something, you should use this with caution.

But of course, changing the registry won’t do anything if Explorer is still running, so I wrote the following batch file to handle that, where RegistryUpdate.reg contains  the new settings:

@echo off

REM need to kill explorer and restart for auto hide
REM task bar to take effect
REM so
REM kill explorer
cmd /c taskkill /F /IM explorer.exe

REM Insert registry changes
regedit /s RegistryUpdate.reg

REM Start explorer (Start tells it to run, but not to wait)
start explorer.exe

 

Second, unlock the taskbar.  This one is fairly simple, just add the following to RegistryUpdate.reg (mentioned above):

;Unlock the Taskbar so that it can be hidden from applications
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced]
     "TaskbarSizeMove"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced]
     "TaskbarSizeMove"=

[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer]
     "LockTaskbar"=-

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer]
     "LockTaskbar"=-

 

Third, disable hot keys.   This is fairly simple, but may need more explanation.  The value “Scancode Map” under the registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout controls disabling of hot keys.   This value is documented on MSDN at http://msdn.microsoft.com/en-us/library/windows/hardware/jj128267(v=vs.85).aspx so I won’t go into all of the details.  The following disables keys that I wanted disabled:

;Disable Windows and other hotkeys
;5b e0 LEft Windows Key
;5c e0 Right Windows Key
;5d e0 Windows Menu Key
;44 00 F10
;1d 00 Left Ctrl
;38 00 Left Alt 
;1d e0 Right Ctrl
;38 e0 Right Alt

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"Scancode Map"=hex:00,00,00,00,00,00,00,00,09,00,00,00,00,00,5b,e0,00,00,5c,e0,00,00,5d,e0,00,00,\
               44,00,00,00,1d,00,00,00,38,00,00,00,1d,e0,00,00,38,e0,00,00,00,00

Finally, disable the taskbar.   This is fairly simple, we need to find a handle to taskbar, then hide the window (different from auto hide) and disable the window.   If we disable it but don’t hide it, it will still be visible.   If we hide it but don’t disable it, it might come back.

In C++, the code looks like this:

void CWin7FullScreenApp::ShowTaskBar( bool Show )
{
               HWND TaskBar = FindWindow(TEXT("Shell_traywnd"), NULL );
               if( TaskBar != NULL )
               {
                              DWORD SWShow = Show ? SW_SHOW : SW_HIDE;
                              ShowWindow( TaskBar, SWShow );
                              EnableWindow( TaskBar, Show );
               }
}

If ShowTaskBar is called with false, the taskbar is hidden.  So, as a test I hide the taskbar when my app starts, and show it when the destructor is called:

CWin7FullScreenApp::CWin7FullScreenApp()
{
               ShowTaskBar( FALSE );
}

CWin7FullScreenApp::~CWin7FullScreenApp()
{
               ShowTaskBar( TRUE );
}

But you may be using C#.   I am leaving out the P\Invokes because they are readily available at pinvoke.net.  The P\Invokes that I used are for ShowWindowCommands, FindWindow, ShowWindow and EnableWindow.

The code is:

public Win7FullScreen()
{
            InitializeComponent();
            ShowTaskBar(false);
}

 

void ShowTaskBar( bool Show )
{
            ShowWindowCommands SWShow = Show ? ShowWindowCommands.Show : ShowWindowCommands.Hide;
            IntPtr hStartBtn = FindWindow("Button", "Start");

                       IntPtr TaskBar = FindWindow("Shell_traywnd", null );
                       if( TaskBar != null )
                       {
                                      ShowWindow( TaskBar, SWShow );
                              EnableWindow( TaskBar, Show );
                       }
         }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            ShowTaskBar(true);
        }

 

 

author: Bruce Eitman | Posted On Tuesday, June 24, 2014 12:01 PM | Comments (0)

WES: Use Command Line to Disable a Service


I have been working with a customer who needs to be able to disable services in WES 7.  They want to do this after using IBW to install WES.

So, how can we manage services from the command line?   The answer is use SC.EXE, the service control manager.   SC.EXE is capable of doing many things, but since all we need to do is disable a service let’s focus on that.   Well, let’s also shutdown the service, which may not really be necessary except that it will let us see what the systems does with the service shutdown.

If you want to know all of the features of SC.EXE, simply run “SC.EXE /?” to see a list.  From there, you can run “SC.EXE <Command>” to get more information on the possible commands.

For this example, I will disable the Windows Update Service.

First, you will need the service name.  To get the service name using SC.EXE, run “SC.EXE query” which will output information about all services currently running.   That is way too much information, so I recommend redirecting the output to a file by running “SC.EXE query >out.txt”.   Searching the output for Windows Update finds:

SERVICE_NAME: wuauserv
DISPLAY_NAME: Windows Update
        TYPE               : 20  WIN32_SHARE_PROCESS 
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, ACCEPTS_PRESHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0

So now we know that the service name is wuauserv.

Now, disable a service.   To disable the service, we need to configure it, so we will use SC.EXE config which has an option to change the start settings.   Run “SC.EXE config wuauserv start= disable” to disable the service.   NOTE:  There is a space between the ‘=’ and disable.

Finally, shutdown the service by running SC stop.   This is simple, just run “SC.EXE stop wuauserv”.   You can confirm that the service stopped by running “SC.EXE query wuauserv”, whih will output:

SERVICE_NAME: wuauserv
        TYPE               : 20  WIN32_SHARE_PROCESS 
        STATE              : 1  STOPPED
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0

 

But, it would be even better to put this into a batch file.   Here it is:

@echo off

call :DisableService wuauserv

goto :EOF

:DisableService
               sc config %1 start= disabled
               sc stop %1
               goto :EOF

 

Which seems like a lot of work to disable only one service, so let’s make it do several services by reading a list from a file:

@echo off
set ServiceListFile=servicelist.txt

FOR /F %%i in (%ServiceListFile%) DO (
               call :DisableService %%i
)

goto :EOF

:DisableService
               sc config %1 start= disabled
               sc stop %1
goto :EOF

 

And servicelist.txt might look like:

ShellHWDetection
CISVC
IISADMIN
wuauserv

 

Now we can disable and shutdown a list of services after running IBW.

author: Bruce Eitman | Posted On Tuesday, June 24, 2014 10:45 AM | Comments (0)

Windows CE: Simple Little Text Editor


 The Simple Little Text Editor can be downloaded from: 
 
I recently decided that it was time to write my own Text Editor. I wrote this for my own needs, but am making it available to you.
My requirements are simple:
1.       Be able to view text files with any file extension.
2.    Be able to edit text files
2.       Need to support multiple CPU families and multiple OS versions – so this targets the .NET CompactFramework version 2.

What I came up with looks like this:


I provide this Simple Little Text Editor to you free of charge and without a license and without a warrantee. You may use it as you see fit, I just ask that you do not provide it to others for download, instead refer them to this page.
This is version 1.x, there will never be a version 2.x because I consider this to be feature complete for a free software download. But if you report bugs using the feedback below, I will work on fixing them. I am not providing the source code like I sometimes do, but if you are interested in obtaining the source code to use to add features I can be talked into that, but not for free.
 The Simple Little Text Editor can be downloaded from: 
  
History
V1.0 - August 19, 2013
First Release
Copyright © 2013 – Bruce Eitman
All Rights Reserved
 

 

author: Bruce Eitman | Posted On Monday, August 19, 2013 10:46 AM | Comments (2)

Simple Little Registry Editor - New Release


I have posted a new release of the Simple Little Registry Editor found in Windows CE: Simple Little Registry Editor

This release fixes a problem with writing DWORD values when the most significant bit is set.  The application uses RegistryKey.SetValue.  There seems to be a problem with how the .NET CompactFramework (and the full framework) handle the second argument during the call which causes an exception.

So the following does not work:

RegistryKey.SetValue( "TestValue", 0xFFFFFFFF, RegistryValueKind.DWord );

But, this does:

RegistryKey.SetValue( "TestValue",unchecked((int) 0xFFFFFFFF), RegistryValueKind.DWord );

Copyright © 2012 – Bruce Eitman
All Rights Reserved

author: Bruce Eitman | Posted On Monday, November 19, 2012 12:40 PM | Comments (0)

C#: My World Clock


 

I have been working on cleaning my office of 8 years of stuff from several engineers working on many projects.  It turns out that we have a few extra single board computers with displays, so at the end of the day last Friday I though why not create a little application to display the time, you know, a clock. 

How difficult could that be?  It turns out that it is quite simple – until I decided to gold plate the project by adding time displays for our offices around the world.

I decided to use C#, which actually made creating the main clock quite easy.   The application was simply a text box and a timer.  I set the timer to fire a couple of times a second, and when it does use a DateTime object to get the current time and retrieve a string to display.

And I could have been done, but of course that gold plating came up.   Seems simple enough, simply offset the time from the local time to the location that I want the time for and display it.    Sure enough, I had the time displayed for UK, Italy, Kansas City, Japan and China in no time at all.

But it is October, and for those of us still stuck with Daylight Savings Time, we know that the clocks are about to change.   My first attempt was to simply check to see if the local time was DST or Standard time, then change the offset for China.  China doesn’t have Daylight Savings Time.

If you know anything about the time changes around the world, you already know that my plan is flawed – in a big way.   It turns out that the transitions in and out of DST take place at different times around the world.   If you didn’t know that, do a quick search for “Daylight Savings” and you will find many WEB sites dedicated to tracking the time changes dates, and times.

Now the real challenge of this application; how do I programmatically find out when the time changes occur and handle them correctly?  After a considerable amount of research it turns out that the solution is to read the data from the registry and parse it to figure out when the time changes occur.

Reading Time Change Information from the Registry

Reading the data from the registry is simple, using the data is a little more complicated.  First, reading from the registry can be done like:

            byte[] binarydata = (byte[])Registry.GetValue("HKEY_LOCAL_MACHINE\\Time Zones\\Eastern Standard Time", "TZI", null);

 

Where I have hardcoded the registry key for example purposes, but in the end I will use some variables.  

We now have a binary blob with the data, but it needs to be converted to use the real data.   To start we will need a couple of structs to hold the data and make it usable.   We will need a SYSTEMTIME and REG_TZI_FORMAT.   You may have expected that we would need a TIME_ZONE_INFORMATION struct, but we don’t.   The data is stored in the registry as a REG_TZI_FORMAT, which excludes some of the values found in TIME_ZONE_INFORMATION.

    struct SYSTEMTIME

    {

        internal short wYear;

        internal short wMonth;

        internal short wDayOfWeek;

        internal short wDay;

        internal short wHour;

        internal short wMinute;

        internal short wSecond;

        internal short wMilliseconds;

    }

 

    struct REG_TZI_FORMAT

    {

        internal long Bias;

        internal long StdBias;

        internal long DSTBias;

        internal SYSTEMTIME StandardStart;

        internal SYSTEMTIME DSTStart;

    }

 

Now we need to convert the binary blob to a REG_TZI_FORMAT.   To do that I created the following helper functions:

        private void BinaryToSystemTime(ref SYSTEMTIME ST, byte[] binary, int offset)

        {

            ST.wYear = (short)(binary[offset + 0] + (binary[offset + 1] << 8));

            ST.wMonth = (short)(binary[offset + 2] + (binary[offset + 3] << 8));

            ST.wDayOfWeek = (short)(binary[offset + 4] + (binary[offset + 5] << 8));

            ST.wDay = (short)(binary[offset + 6] + (binary[offset + 7] << 8));

            ST.wHour = (short)(binary[offset + 8] + (binary[offset + 9] << 8));

            ST.wMinute = (short)(binary[offset + 10] + (binary[offset + 11] << 8));

            ST.wSecond = (short)(binary[offset + 12] + (binary[offset + 13] << 8));

            ST.wMilliseconds = (short)(binary[offset + 14] + (binary[offset + 15] << 8));

        }

 

 

        private REG_TZI_FORMAT ConvertFromBinary(byte[] binarydata)

        {

            REG_TZI_FORMAT RTZ = new REG_TZI_FORMAT();

 

            RTZ.Bias = binarydata[0] + (binarydata[1] << 8) + (binarydata[2] << 16) + (binarydata[3] << 24);

            RTZ.StdBias = binarydata[4] + (binarydata[5] << 8) + (binarydata[6] << 16) + (binarydata[7] << 24);

            RTZ.DSTBias = binarydata[8] + (binarydata[9] << 8) + (binarydata[10] << 16) + (binarydata[11] << 24);

            BinaryToSystemTime(ref RTZ.StandardStart, binarydata, 4 + 4 + 4);

            BinaryToSystemTime(ref RTZ.DSTStart, binarydata, 4 + 16 + 4 + 4);

 

            return RTZ;

        }

 

I am the first to admit that there may be a better way to get the settings from the registry and into the REG_TXI_FORMAT, but I am not a great C# programmer which I have said before on this blog.   So sometimes I chose brute force over elegant.

Now that we have the Bias information and the start date information, we can start to make sense of it.   The bias is an offset, in minutes, from local time (if already in local time for the time zone in question) to get to UTC – or as Microsoft defines it: UTC = local time + bias.  Standard bias is an offset to adjust for standard time, which I think is usually zero.   And DST bias is and offset to adjust for daylight savings time.

Since we don’t have the local time for a time zone other than the one that the computer is set to, what we first need to do is convert local time to UTC, which is simple enough using:

                DateTime.Now.ToUniversalTime();

Then, since we have UTC we need to do a little math to alter the formula to: local time = UTC – bias.  In other words, we need to subtract the bias minutes.

I am ahead of myself though, the standard and DST start dates really aren’t dates.   Instead they indicate the month, day of week and week number of the time change.   The dDay member of SYSTEM time will be set to the week number of the date change indicating that the change happens on the first, second… day of week of the month.  So we need to convert them to dates so that we can determine which bias to use, and when to change to a different bias.   To do that, I wrote the following function:

        private DateTime SystemTimeToDateTimeStart(SYSTEMTIME Time, int Year)

        {

            DayOfWeek[] Days = { DayOfWeek.Sunday,

DayOfWeek.Monday,

DayOfWeek.Tuesday,

DayOfWeek.Wednesday,

DayOfWeek.Thursday,

DayOfWeek.Friday,

DayOfWeek.Saturday };

            DateTime InfoTime = new DateTime(Year,

Time.wMonth,

Time.wDay == 1 ? 1 : ((Time.wDay - 1) * 7) + 1,

Time.wHour,

Time.wMinute,

Time.wSecond,

DateTimeKind.Utc);

            DateTime BestGuess = InfoTime;

            while (BestGuess.DayOfWeek != Days[Time.wDayOfWeek])

            {

                BestGuess = BestGuess.AddDays(1);

            }

            return BestGuess;

        }

 

SystemTimeToDateTimeStart gets two parameters; a SYSTEMTIME and a year.   The reason is that we will try this year and next year because we are interested in start dates that are in the future, not the past.  The function starts by getting a new Datetime with the first possible date and then looking for the correct date.

Using the start dates, we can then determine the correct bias to use, and the next date that time will change:

            NextTimeChange = StandardChange;

            CurrentBias = TimezoneSettings.Bias + TimezoneSettings.DSTBias;

            if (DSTChange.Year != 1 && StandardChange.Year != 1)

            {

                if (DSTChange.CompareTo(StandardChange) < 0)

                {

                    NextTimeChange = DSTChange;

                    CurrentBias = TimezoneSettings.StdBias + TimezoneSettings.Bias;

                }

            }

            else

            {

                // I don't like this, but it turns out that China Standard Time

                // has a DSTBias of -60 on every Windows system that I tested.

                // So, if no DST transitions, then just use the Bias without

                // any offset

                CurrentBias = TimezoneSettings.Bias;

            }

 

Note that some time zones do not change time, in which case the years will remain set to 1.   Further, I found that the registry settings are actually wrong in that the DST Bias is set to -60 for China even though there is not DST in China, so I ignore the standard and DST bias for those time zones.

There is one thing that I have not solved, and don’t plan to solve.  If the time zone for this computer changes, this application will not update the clock using the new time zone.  I tell  you this because you may need to deal with it – I do not because I won’t let the user get to the control panel applet to change the timezone.

Copyright © 2012 – Bruce Eitman

All Rights Reserved

author: Bruce Eitman | Posted On Thursday, October 25, 2012 2:14 PM | Comments (2)

Windows CE: Changing Static IP Address


A customer contacted me recently and asked me how to change a static IP address at runtime.  Of course this is not something that I know how to do, but with a little bit of research I figure out how to do it.

It turns out that the challenge is to request that the adapter update itself with the new IP Address.  Otherwise, the change in IP address is a matter of changing the address in the registry for the adapter.   The registry entry is something like:

[HKEY_LOCAL_MACHINE\Comm\LAN90001\Parms\TcpIp]
    "EnableDHCP"=dword:0
    "IpAddress"="192.168.0.100"
    "DefaultGateway"="192.168.0.1"
    "Subnetmask"="255.255.255.0"

Where LAN90001 would be replace with your adapter name.  I have written quite a few articles about how to modify the registry, including a registry editor that you could use.

Requesting that the adapter update itself is a matter of getting a handle to the NDIS driver, and then asking it to refresh the adapter.  The code is:

#include <windows.h>

#include "winioctl.h"

#include "ntddndis.h"

 

void RebindAdapter( TCHAR *adaptername )

{

      HANDLE hNdis;

      BOOL fResult = FALSE;

      int count;

 

      // Make this function easier to use - hide the need to have two null characters.

      int length = wcslen(adaptername);

      int AdapterSize = (length + 2) * sizeof( TCHAR );

      TCHAR *Adapter = malloc(AdapterSize);

      wcscpy( Adapter, adaptername );

      Adapter[ length ] = '\0';

      Adapter[ length +1 ] = '\0';

 

 

      hNdis = CreateFile(DD_NDIS_DEVICE_NAME,

                  GENERIC_READ | GENERIC_WRITE,

                  FILE_SHARE_READ | FILE_SHARE_WRITE,

                  NULL,

                  OPEN_ALWAYS,

                  0,

                  NULL);

 

      if (INVALID_HANDLE_VALUE != hNdis)

      {

            fResult = DeviceIoControl(hNdis,

                        IOCTL_NDIS_REBIND_ADAPTER,

                        Adapter,

                        AdapterSize,

                        NULL,

                        0,

                        &count,

                        NULL);

            if( !fResult )

            {

                  RETAILMSG( 1, (TEXT("DeviceIoControl failed %d\n"), GetLastError() ));

            }

            CloseHandle(hNdis);

      }

      else

      {

            RETAILMSG( 1, (TEXT("Failed to open NDIS Handle\n")));

      }

 

}

 

 

 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR    lpCmdLine, int       nCmdShow)

{

    RebindAdapter( TEXT("LAN90001") );

    return 0;

}

 

If you don’t want to write any code, but instead plan to use a registry editor to change the IP Address, then there is a command line utility to do the same thing.  NDISConfig.exe can be used:

Ndisconfig adapter rebind LAN90001

  

Copyright © 2012 – Bruce Eitman

All Rights Reserved

author: Bruce Eitman | Posted On Wednesday, October 24, 2012 9:00 PM | Comments (2)

Windows CE: C# Application to Format TFAT


Windows CE: C# Application to Format TFAT
In Windows CE: Formatting TFAT I show how to format a disk with TFAT using C/C++ code. The customer that I wrote that for develops using C#, so I wrote a C# application to test the formatting code. To me, most formatting tools are just too difficult for the average user. In this case, the user will be someone working in the factory or field service. They just need to select a disk and request that it be formatted.
The application dialog look like:
It has a drop down list which I populated with a list of disks, a format button and an exit button. It is simple, and easy to use.
The hard part of this application is populating the drop down list. I did some searching and found suggestion of using System.IO.DirectoryInfo to find the root folders and check the attributes for temporary folders. The following code demonstrates this approach, were DiskSelect is the drop down list:
            System.IO.DirectoryInfo DI = new System.IO.DirectoryInfo(@"\");
 
            System.IO.DirectoryInfo[] Folders = DI.GetDirectories();
            foreach (System.IO.DirectoryInfo FDI in Folders)
            {
                if (FDI.Attributes.CompareTo(System.IO.FileAttributes.Temporary) == 1)
                    DiskSelect.Items.Add(FDI.Name);
            }
            if( DiskSelect.Items.Count > 0 )
               DiskSelect.SelectedIndex = 0;
This code works and can be effective on some systems. But it has a flaw which is that not all temprary folders are disks. If your OS includes the network redirector component, there will be a Network folder which is temporary but is not a disk. The Release folder which is used in debugging the OS using Platform Builder is also temporary, but not a disk.
The only alternative that I could find is to use FindFirstStore()/FindNextStore() and FindFirstPartition()/FindNextPartition() which are included in StoreAPI. The downside of this approach is that StoreAPI is not generaly included in the SDK and worse, it is not a DLL in the system so it cannot be P/Invoked. So if you don’t have access to the StoreAPI, you might as well stop reading now – the rest of this assumes that you do have access to StoreAPI. If you don’t have it, you might want to ask your OEM for it because it has some valuable functions for accessing disk information.
I could have simply wrapped the StoreAPI with a DLL, which would have been as simple as just linking StoreAPI.lib with a DllEntry() function. But I am not a good enough C# programmer to define the structures needed for C# and I felt that for a C# program the functions are just too clunky. Instead I made some assumptions before writing the code. The assumptions are:
1.       The functions that I will be writing will be used for populating a drop down list and nothing more sophisticated
2.       The disks have only one partition, if you are working with systems that have multiple partitions you can extend the code to handle that but I didn’t need it
I realized that I needed to have some data that I could let the application track and pass into function calls. So I started by defining the structure that I needed:
typedef struct __FindDiskInfo__
{
                STOREINFO StoreInfo;
                PARTINFO PartInfo;
} FindDiskInfoT;
STOREINFO and PARTINFO are defined in storemgr.h which is included in the SDK. I plan to create functions that populate the structure and then some functions to get the strings that I need. This should make the C# fairly easy to write. The first step is to initalize the structure and get a pointer/reference from the C/C++ code to the C# code. NewStoreInfo() will allocate the storage and initialize it and DeleteStoreInfo() will release the structure:
__declspec(dllexport) FindDiskInfoT * APIENTRY NewStoreInfo()
{
                FindDiskInfoT *FindDiskInfo;
                RETAILMSG( 1, (TEXT("NewStoreInfo\n")));
                FindDiskInfo = (FindDiskInfoT *)malloc( sizeof(FindDiskInfoT) );
                if( FindDiskInfo == NULL )
                {
                                RETAILMSG( 1, (TEXT("StartFindStore: malloc failed\n")));
                                return NULL;
                }
                memset(FindDiskInfo, 0, sizeof(FindDiskInfoT));
                FindDiskInfo->StoreInfo.cbSize = sizeof(STOREINFO);
                FindDiskInfo->PartInfo.cbSize = sizeof(PARTINFO);
                                wcscpy(FindDiskInfo->PartInfo.szVolumeName, TEXT("TEST VolName"));
 
                RETAILMSG( 1, (TEXT("NewStoreInfo return %X\n"), FindDiskInfo));
                return FindDiskInfo;
}
 
__declspec(dllexport) BOOL APIENTRY DeleteStoreInfo(HANDLE hFind, FindDiskInfoT *FindDiskInfo)
{
                if( FindDiskInfo != NULL )
                {
                                free( FindDiskInfo );
                }
                if( hFind )
                {
                                FindCloseStore( hFind );
                }
                return TRUE;
}
And the C# call to these function is:
IntPtr StoreInfo;
 
StoreInfo = NewStoreInfo();
DeleteStoreInfo(hStore, StoreInfo);
Note that DeleteStoreInfo() has an hStore passed into into it. hStore is HANDLE used for finding the disks. hStore is returned from StartFindDisk() which is called to start finding available disks. StartFindDisk() receives a pointer to the previously allocated FindDiskInfoT:
__declspec(dllexport) HANDLE APIENTRY StartFindDisk( FindDiskInfoT *FindDiskInfo )
{
HANDLE hFind = INVALID_HANDLE_VALUE;
 
                hFind = FindFirstStore(&(FindDiskInfo->StoreInfo));
               
                if( hFind != INVALID_HANDLE_VALUE )
                {
                                HANDLE hStore;
                                hStore = OpenStore( FindDiskInfo->StoreInfo.szDeviceName );               
 
                                if( hStore )
                                {
                                                HANDLE hFind = INVALID_HANDLE_VALUE;
                                                hFind = FindFirstPartition(hStore, &(FindDiskInfo->PartInfo));
 
                                                if(INVALID_HANDLE_VALUE != hFind)
                                                {
                                                                FindClose(hFind);
                                                }
                                                else
                                                                RETAILMSG( 1, (TEXT("No Partitions Found\n")));
                                                CloseHandle(hStore);
                                }
                                else
                                                RETAILMSG(1, (TEXT("OpenSelectedStore failed\n")));
                }
                else
                {
                                RETAILMSG( 1, (TEXT("StartFindStore: FindFirstStore failed %d\n"), GetLastError()));
                }
               
                return hFind;
}
And the C# call is:
            hStore = StartFindDisk(StoreInfo);
StartFindDisk() finds the first available disk and the first partition on the disk and populates the FindDiskInfoT structure with the available information for use later. After calling this, to get the name of the folder, the device name and the partition name, the following functions can be used:
__declspec(dllexport) TCHAR * APIENTRY GetStoreDeviceName( FindDiskInfoT *FindDiskInfo )
{
                TCHAR * RetVal = NULL;
                if( FindDiskInfo )
                {
                                RetVal = FindDiskInfo->StoreInfo.szDeviceName;
                }
                return RetVal;
}
 
__declspec(dllexport) TCHAR * APIENTRY GetStoreFolderName( FindDiskInfoT *FindDiskInfo )
{
                TCHAR * RetVal = NULL;
                if( FindDiskInfo )
                {
                                RetVal = FindDiskInfo->PartInfo.szVolumeName;
                }
                return RetVal;
}
 
__declspec(dllexport) TCHAR * APIENTRY GetStorePartitionName( FindDiskInfoT *FindDiskInfo )
{
                TCHAR * RetVal = NULL;
                if( FindDiskInfo )
                {
                                RetVal = FindDiskInfo->PartInfo.szPartitionName;
                }
                return RetVal;
}
Then to get the rest of the disks, FindNextDisk() is used:
__declspec(dllexport) BOOL APIENTRY FindNextDisk( HANDLE hFind, FindDiskInfoT *FindDiskInfo )
{
BOOL FindResult = FALSE;
 
                if( FindDiskInfo == NULL || hFind == INVALID_HANDLE_VALUE )
                {
                                                RETAILMSG( 1, (TEXT("FindNextDisk: invalid parameters\n")));
                                                return FALSE;
                }
                FindResult = FindNextStore(hFind, &(FindDiskInfo->StoreInfo));
               
                if( FindResult != FALSE )
                {
                                HANDLE hStore;
                                hStore = OpenStore( FindDiskInfo->StoreInfo.szDeviceName );               
 
                                if( hStore )
                                {
                                                HANDLE hFind = INVALID_HANDLE_VALUE;
                                                memset( &(FindDiskInfo->PartInfo), 0, sizeof(PARTINFO));
                                                FindDiskInfo->PartInfo.cbSize = sizeof(PARTINFO);
 
                                                hFind = FindFirstPartition(hStore, &(FindDiskInfo->PartInfo));
 
                                                if(INVALID_HANDLE_VALUE != hFind)
                                                {
                                                                FindClose(hFind);
                                                }
                                                else
                                                {
                                                                RETAILMSG( 1, (TEXT("FindNextDisk: No Partitions Found %d\n"), GetLastError()));
                                                                FindResult = FALSE;
                                                }
                                                CloseHandle(hStore);
                                }
                                else
                                {
                                                RETAILMSG(1, (TEXT("FindNextDisk: OpenSelectedStore failed\n")));
                                                FindResult = FALSE;
                                }
                }
                else
                {
                                RETAILMSG( 1, (TEXT("FindNextDisk: FindNextStore failed %d\n"), GetLastError()));
                                FindResult = FALSE;
                }
               
                return FindResult;
}
Similar to StartFindDisk() this finds the next disk and its first partition and populates the FindDiskInfoT with the available data, so that the strings can be retreived later.
The code for setting up the C# dialog looks like:
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool FormatDiskTFAT(String StoreName, String PartitionName);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool FormatDiskTFATEx(String FolderName);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr StartFindDisk(IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool DeleteStoreInfo(IntPtr hStore, IntPtr FindDiskInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool FindNextDisk(IntPtr hFind, IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr GetStoreDeviceName(IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr GetStoreFolderName(IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr GetStorePartitionName(IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr NewStoreInfo();
 
        DiskInfo[] Disks;
 
        public FormatTool()
        {
            IntPtr StoreInfo;
            IntPtr hStore;
            IntPtr ipName;
            int DiskCount = 0;
 
            InitializeComponent();
          
            StoreInfo = NewStoreInfo();
            hStore = StartFindDisk(StoreInfo);
 
            if (hStore != null)
            {
                Disks = new DiskInfo[1];
                Disks[DiskCount] = new DiskInfo();
 
                ipName = GetStoreDeviceName(StoreInfo);
                Disks[DiskCount].DeviceName = Marshal.PtrToStringUni(ipName).ToString();
 
                ipName = GetStoreFolderName(StoreInfo);
                Disks[DiskCount].FolderName = Marshal.PtrToStringUni(ipName).ToString();
 
                ipName = GetStorePartitionName(StoreInfo);
                Disks[DiskCount].PartitionName = Marshal.PtrToStringUni(ipName).ToString();
 
                DiskSelect.Items.Add(Disks[DiskCount].FolderName);
                DiskSelect.SelectedIndex = 0;
                DiskCount++;
 
                while (FindNextDisk(hStore, StoreInfo))
                {
                    DiskInfo[] NewDisks = new DiskInfo[DiskCount+1];
                    Disks.CopyTo(NewDisks, 0);
                    NewDisks[DiskCount] = new DiskInfo();
 
                    ipName = GetStoreDeviceName(StoreInfo);
                    NewDisks[DiskCount].DeviceName = Marshal.PtrToStringUni(ipName).ToString();
 
                    ipName = GetStoreFolderName(StoreInfo);
                    NewDisks[DiskCount].FolderName = Marshal.PtrToStringUni(ipName).ToString();
 
                    ipName = GetStorePartitionName(StoreInfo);
                    NewDisks[DiskCount].PartitionName = Marshal.PtrToStringUni(ipName).ToString();
 
                    DiskSelect.Items.Add(NewDisks[DiskCount].FolderName);
                    DiskCount++;
                    Disks = NewDisks;
                }
                DeleteStoreInfo(hStore, StoreInfo);
                if (DiskSelect.Items.Count > 0)
                    DiskSelect.SelectedIndex = 0;
            }
            
        }
I probably could have simplified this code, and if it were a long term project I would spend time on that.   This cod uses a class for keeping track of the disk information for use in the call to FormatDiskTFAT:
    public class DiskInfo
    {
        public String FolderName;
        public String DeviceName;
        public String PartitionName;
    }
But in hindsight I wrote FormatDiskTFATEx() which doesn’t need all of that information. So the code can be simplified to:
            String DiskName;
            StoreInfo = NewStoreInfo();
            hStore = StartFindDisk(StoreInfo);
 
            if (hStore != null)
            {
                ipName = GetStorePartitionName(StoreInfo);
                DiskName = Marshal.PtrToStringUni(ipName).ToString();
 
                DiskSelect.Items.Add(DiskName);
                DiskSelect.SelectedIndex = 0;
 
                while (FindNextDisk(hStore, StoreInfo))
                {
                    ipName = GetStorePartitionName(StoreInfo);
                    DiskName = Marshal.PtrToStringUni(ipName).ToString();
 
                    DiskSelect.Items.Add(DiskName);
                }
                DeleteStoreInfo(hStore, StoreInfo);
                if (DiskSelect.Items.Count > 0)
                    DiskSelect.SelectedIndex = 0;
            }
Finally, when the Format button is pressed we need to format the disk. The button handler is:
        private void FormatButton_Click(object sender, EventArgs e)
        {
            int Index = DiskSelect.SelectedIndex;
 
            Exitbutton.Visible = false;
            FormatButton.Visible = false;
            Formatinglabel.Text = "Formatting, Please Wait";
            Formatinglabel.Visible = true;
            this.Refresh();
 
            System.Threading.Thread.Sleep(2000); 
            FormatDiskTFAT(Disks[Index].DeviceName, Disks[Index].PartitionName);
 
            Exitbutton.Visible = true;
            FormatButton.Visible = true;
            Formatinglabel.Text = "Formatting Complete";
            Formatinglabel.Visible = true;
            this.Refresh();
        }
Which uses FormatDiskTFAT(), but FormatDiskTFATEx() can be used as:
            FormatDiskTFATEx(DiskSelect.SelectedItem.ToString());
FormatButton_Click() hides the buttons and puts a message in a static text box to tell the user to wait then formats the disk. After the disk is formatted, it changes the text to Format Complete and puts the buttons back in. The problem that I found is that the format can be very quick, too quick so that the message isn’t displayed and the buttons aren’t removed. So I added a 2 second delay just to let the user know that something happened.
 
Copyright © 2010 – Bruce Eitman
All Rights Reserved

author: Bruce Eitman | Posted On Monday, August 30, 2010 10:34 PM | Comments (3)