High Performance Timer

Below is a sample class containing a high performance timer. Example usuage:

PerformanceTimer pf = new PerformanceTimer(true);

float timeMs = pf.GetTimeMs();

using System;
using System.Runtime.InteropServices;

///
/// This class can be used to gather accurate performance timings.
///
public class PerformanceTimer
{


[DllImport("kernel32.dll")]
internal static extern bool QueryPerformanceCounter(ref long lpPerformanceCount);



[DllImport("kernel32.dll")]
internal static extern bool QueryPerformanceFrequency(ref long lpFrequency);



#region Private Variables

private bool _isRunning = false;
private long _startTime = 0;
private long _frequency = 0;
private float _elapsed = 0;

#endregion Private Variables



#region Constructors



///
/// Default constructor - does not start timer immediately.
///
public PerformanceTimer()
{

}



///
/// Constructor with option to start timer at same time.
///
///Whether to start timer immediately.

public PerformanceTimer(bool startTimer)

{

    if (startTimer)
    {

        Start();

    }

}

#endregion Constructors


#region Public Properties

///
/// Returns True if the timer is currently running.
///
/// To change the running state use the Start and Stop methods provided.

public bool IsRunning
{
    get { return _isRunning; }
}



#endregion Public Properties



#region Public Methods



///
/// Starts the performance timer running.
///

public void Start()

{
    QueryPerformanceFrequency(ref _frequency);
    QueryPerformanceCounter(ref _startTime);
    _elapsed = 0;
    _isRunning = true;

}



///
/// Stops the performance timer running.
///

public void Stop()

{

    // Call procedure that will set internal elapsed variable, then turn running flag off.

    GetTime();

    _isRunning = false;

}



///

/// Returns the time elapsed since "Start" was called (in seconds). If the timer

/// is not running the time elapsed between the last start and stop time is returned.

///

/// A float representing the time ellapsed (in seconds)

public float GetTime()

{

    if (_isRunning)
    {
        long endTime = 0;
        QueryPerformanceCounter(ref endTime);
        _elapsed = (float)(endTime - _startTime) / _frequency;
        return _elapsed;
    }
    else
    {
        return _elapsed;
    }
}



///
/// Returns the time elapsed since "Start" was called (in milliseconds). If the timer
/// is not running the time elapsed between the last start and stop time is returned.
///
/// A float representing the time ellapsed (in milliseconds)

public float GetTimeMs()

{
    if (_isRunning)
    {
        return GetTime() * 1000;
    }
    else
    {
        return _elapsed * 1000;

    }

}



///
/// Override ToString to display time nicely formatted.
///
/// Returns the time formatted as a string.

public override string ToString()

{
    return String.Format("{0:0000.000}s ", GetTime());
}
#endregion Public Methods

}

Importing Data From an Excel Workbook into a VB array

The following code can be used to import the contents of an excel workbook into an array from within a Visual Basic application.

Option Explicit

'Purpose     :  Reads an excel workbook's worksheet into a 2d array
'Inputs      :  sWorkbookPath           The path of the workbook to return the data from.
'               [sSheetName]            The name of the worksheet to return the data from.
'Outputs     :  Returns True if successful
'Author      :  Andrew Baker
'Date        :  31/12/2000 13:51
'Notes       :
'Revisions   :

Function ImportWorksheetFromExcel(sWorkbookPath As String, Optional sSheetName As String = "") As Variant
    Dim oExcel As Object
    Dim oWorkbook As Object
    Dim oWkSheet As Object
    Dim avValues As Variant
    
    On Error Resume Next
    'Check if file exists
    If Len(Dir$(sWorkbookPath)) > 0 Then
        Set oExcel = CreateObject("Excel.Application")
        If (oExcel Is Nothing) = False Then
            On Error GoTo ErrFailed
            'Successfully created an Excel Application
            'Open workbook
            Set oWorkbook = oExcel.Workbooks.Open(sWorkbookPath, False, True)
            'Add sheet to store results
            If Len(sSheetName) > 0 Then
                Set oWkSheet = oWorkbook.Sheets(sSheetName)
            Else
                'Just use first sheet
                Set oWkSheet = oWorkbook.Sheets(1)
            End If
            
            'Get used range
            Call RangeToArray(oWkSheet.UsedRange, avValues)
            
            'Close Excel
            oWorkbook.Close False
            oExcel.Quit
            Set oExcel = Nothing
        End If
    End If
    
    ImportWorksheetFromExcel = avValues
    Exit Function

ErrFailed:
    Debug.Assert False
    Debug.Print Err.Description
    On Error GoTo 0
End Function

'Purpose     :  Reads the values of a range into an array (much quicker than looping through a range)
'Inputs      :  rngInput                The range to extract the values from.
'               avValues                See outputs.
'Outputs     :  Returns the True on success.
'               avValues                An 2d array containing the values in the range.
'Author      :  Andrew Baker
'Date        :  31/12/2000 13:51
'Notes       :
'Revisions   :
'Example     :  Call RangeToArray(Worksheets(1).Range("A1:K1000"), avValues)

Function RangeToArray(rngInput As Object, avValues As Variant) As Boolean
    On Error GoTo ErrFailed
    avValues = Empty
    avValues = rngInput.Value
    RangeToArray = True
    
    Exit Function

ErrFailed:
    'Failed
    Debug.Print "Error in RangeToArray: " & Err.Description
    Debug.Assert False
    RangeToArray = False
    On Error GoTo 0
End Function

'Demonstration routine.
Sub Test()
    Dim avData As Variant, vCell As Variant

    avData = ImportWorksheetFromExcel("C:\Book1.xls")
    For Each vCell In avData
        Debug.Print "Cell: " + vCell
    Next
End Sub

Simple function to Add a number of weekdays to a date

Below is a simple function to add or subtract a number of weekdays to a specified date.

/// <summary>
/// Takes a reference date and add or subtracts a specified number of weekdays.
/// </summary>
/// <param name="date">The reference date.</param>
/// <param name="offset">The number of days to offset by.</param>
/// <returns>The reference date plus or minus the specified number of weekdays.</returns>
private DateTime AddWeekDays( DateTime date, int offset)
{
	int daysAdded = 0;
	int addDay = 1;
	if (offset < 0)
	{
		addDay = -1;
	}
	DateTime result = date;
	do
	{
		if (daysAdded == offset)
		{
			break;
		}
		if (result.DayOfWeek != DayOfWeek.Saturday && result.DayOfWeek != DayOfWeek.Sunday)
		{
			daysAdded = daysAdded + addDay;
		}
		result = result.AddDays(addDay);
	} while (true);
	return result;
}

A lightweight alternative to Process.GetCurrentProcess().ProcessName

Unfortunately, the following (seemingly harmless code) requires administrator rights (and on Windows 2003 you will need to be member of “Performance Monitor Users” group):

System.Diagnostics.Process.GetCurrentProcess().ProcessName

In most cases this is not appropriate and certainly not in a Citrix environment. If you call it without the correct permissions you the following stack dump:

Unhandled exception in EntryPoint: System.InvalidOperationException: Couldn't get process information from remote machine. ---> System.ComponentModel.Win32Exception: Access is denied    
 at System.Diagnostics.PerformanceMonitor.GetData(String item)    
 at System.Diagnostics.PerformanceCounterLib.GetPerformanceData(String item)    
 at System.Diagnostics.PerformanceCounterLib.get_CategoryTable()    
 at System.Diagnostics.PerformanceCounterLib.GetPerformanceData(String[] categories, Int32[] categoryIndexes)    
 at System.Diagnostics.NtProcessManager.GetProcessInfos(PerformanceCounterLib library)   
  --- End of inner exception stack trace ---    
 at System.Diagnostics.NtProcessManager.GetProcessInfos(PerformanceCounterLib library)    
 at System.Diagnostics.NtProcessManager.GetProcessInfos(String machineName, Boolean isRemoteMachine)    
 at System.Diagnostics.ProcessManager.GetProcessInfos(String machineName)    
 at System.Diagnostics.Process.EnsureState(State state)    
 at System.Diagnostics.Process.get_ProcessName()    

Below is a light weight alternative to System.Diagnostics.Process.GetCurrentProcess().ProcessName and does not require any special permissions:

/// <summary>
/// Returns the starting process name (same as System.Diagnostics.Process.GetCurrentProcess().ProcessName),
/// but doesn't require any admin rights.
/// </summary>
/// <returns>Returns the starting process name.</returns>
public static string GetCurrentProcessNameLite()
{
	string procName = new System.IO.FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location).Name;
	if (procName.ToLower().IndexOf(".exe") > -1)
	{
		// Remove the ".exe" extension
		procName = procName.Substring(0, procName.Length - 4);
	}
	return procName;
}

Managing Terminal Services Sessions Remotely

I regularly administer Windows 2000 Server and Windows Server 2003 machine using Terminal Services. In W2K I use Terminal Services in Administration mode and in W2K3 I use Remote Desktop. Basically this is the same thing but it’s packaged a bit differently on the two OSes. In both situations there are only 2 users allowed for administration so occasionally both are used up and I can’t access the server. This may happen if two people are legitimately using the servers or if someone forgot to log off. Yes, I do have the settings set to automatically disconnect and/or log off a user after X minutes of inactivity but even with that there are times when I need to be able to manage this remotely.

You would think this would be as easy as connecting to the remote server by adding the Terminal Services Manager snap-in to a MMC console or remotely stopping and starting the service (in a pinch). Strangely, these obvious solutions aren’t available. Terminal Services Manager doesn’t exist as a snap-in to connect to a remote machine and because the TermService service is a core system service, you can’t even stop it on the local machine, let alone a remote one.

Fortunately there is an easy solution. Windows 2000+ (includes Windows XP and 2003) have two command-line tools called qwinsta and rwinsta that can query and reset a remote session.

For example, let’s say that I can’t gain access to a server using Terminal Services because both sessions are used up. I can use another server to check the status of the first one. As long as the logged in user has administrative rights on the non-accessible machine I would run this:

qwinsta /server:12.12.12.12

Where 12.12.12.12 is the IP address or name of the non-accessible machine.

This will display something like this:

   qwinsta /server:12.12.12.12
    SESSIONNAME       USERNAME                 ID  STATE   TYPE        DEVICE
    console                                     0  Conn    wdcon
    rdp-tcp                                 65536  Listen  rdpwd
    rdp-tcp#470       Bob                       1  Active  rdpwd
    rdp-tcp#471       Jane                      3  Active  rdpwd 

Now I know that Bob and Jane are the two that are logged in. Since Jane left the office 20 minutes ago I know that she forgot to log off. I don’t know where Bob is but I only need one session so I’ll ignore him for now.

To disconnect Jane’s session I would type this:

rwinsta /server:12.12.12.12 3

Notice the 3 which is the session ID I found from using qwinsta above.

Yet another option with W2K3 is a new feature that lets you connect to the desktop directly and gain a 3rd session.

If you’re like me, you probably noticed that rwinsta and qwinsta seems like strange names. This came from the Citrix Metaframe days which Terminal Services has descended from. It stands for:

qwinsta = Query WINdows STAtion
rwinsta = Reset WINdows STAtion

One final comment. Microsoft has replaced these two tools with Query but since qwinsta and rwinsta is fully compatible with W2K WinXP and W2K3 I choose to use it instead.

Note: In Windows Server 2003, you can right-click on the root in Terminal Services Manager and Connect to another server. Also, if you are in an Active Directory domain and using Windows 2000 Server, you can Connect to All Servers and access remote servers from the GUI. But, in a Windows 2000 Server not connected to a domain, you run into the limitations that prompted this blog.