Contents tagged with Tippsamp;Tricks

  • Code instrumentation with TraceSource - My personal vade mecum

    When writing more complex code you cannot really step through during debugging, it´s helpful to put stud it with statements tracing the execution flow. The .NET Framework provides for this purpose the System.Diagnostics namespace. But whenever I just quickly wanted to use it, it turned out to be a hassle to get the tracing running properly. That´s why I wrote down the following, to make it easier next time.

    Read more...

  • Single Assembly Deployment of Managed and Unmanaged Code

    .NET developers love XCOPY deployment. And they love single assembly components. At least I always feel kinda uneasy, if I have to use some component and need remember a list of files to also include with the main assembly of that component. So when I recently had to develop a managed code component and had to augment it with some unmanaged code from a C DLL (thx to Marcus Heege for helping me with this!), I thought about how to make it easier to deploy the two DLLs. If this were just two assemblies I could have used ILmerge to pack them up in just one file. But this doesn´t work for mixed code components with managed as well as unmanaged DLLs.

    Read more...

  • Inversion of Control Using Generics - Revisiting the Separation of Use and Implementation

    Martin Fowler in his famous article "Inversion of Control Containers and the Dependency Injection pattern" has compiled a number of ways how to dynamically bind a client to a service. I now would like to add two points to the discussion: firstly a distinction regarding what is injected, and secondly a new pattern for injection based on generics. During my discusson I´ll use the same sample scenario as Martin to make it easy to see what I´m trying to add. Here´s a quick recap using C#.

    Read more...

  • Smooth unit testing with NUnit and VB.NET

    Unit testing .NET Fx projects basically is easy with NUnit and I don´t want to add to the literature on unit testing at this point.

    However, the devil´s in the detail. To make unit testing a really smooth experience from within VS.NET, the test run should behave like a regular program run as much as possible. Even or especially when testing functionality in a DLL.

    For me that means at least:

    -Code tested needs access to an app.config file.
    -Debugging should be possible if necessary.
    -Test errors should be treated like compilation errors in that I can easily find the offending source code line.

    Fortunately NUnit fulfills all three requirements, but needs to be tweaked a little bit.

     

    1. Your VS.NET project

    Setup your VS.NET project as usual.

    Be sure, though, to include a reference to the nunit.framework assembly.

    Then add test fixture classes to the project.

    Compile the project.

    2. App.Config

    Add an app.config file to the VS.NET project if needed.

    Do not rename the config file!

    3. Setup NUnit project

    Start the NUnit GUI and setup an NUnit project for your VS.NET project. Reference your project´s assembly.

    Then go to Project|Edit and change the Config file name to App.Config.

    Save the NUnit project which should be named like your VS.NET project and located in the same directory.

    4. Autostart NUnit When Debugging

    Go back to your VB.NET VS.NET project, and open the project properties.

    Go to Configuration properties|Debugging and enter into Start external program:

        C:\Program Files\NUnit V2.1\bin\nunit-console.exe

    (If needed adapt the path to nunit-console.exe.)

    Enter into Start Options|Command line arguments:

        "..\myapp.nunit" /wait /nologo

    The first parameter is the path pointing to the NUnit project located in the same directory as your myapp.vbproj file.

    (Change "myapp" to the name of your application´s name. The ".." is necessary, since the startup dir for nunit-console.exe is the bin directory of your application.)

     

    Now run the library project in debug mode by pressing F5 in VS.NET. The NUnit console test runner should start, run all tests and show the results. If you like, you can set breakpoints in your code and the test runner will be halted when it reaches them. And you can access your app.config file as usual from within your code, since the test runner will register it with the application domain it creates for executing the tests.

    Only one item is still missing from the above requirements lists:

    5. Create a Debug Version of NUnit

    When running the console test runner all results are shown in its console window. That´s informative, but puts the burden on you to locate the errorenous lines in your source code.

    Now the NUnit documentation states, the test runner would detect if it´s run in VS.NET debug mode and output error messages to the VS.NET output window. By clicking on them you are supposed to be taken to the source code.

    Alas, at least in my version of NUnit (2.1) this did not work out of the box. No messages showed up in the VS.NET output pane. The reason for this obviously is, the nunit-console.exe test runner included in the .msi installation file has been compiled in release mode.

    So what you have to do is open the NUnit V2.1\src\nunit-console\nunit-console.csproj project and recompile the test runner source in debug mode. (No changes to the source code are necessary. But to accomplish that, it´s best to copy the nunit-console directory someplace else (e.g. desktop), open the project, and add references to the nunit.framework.dll and nunit.util.dll. If you don´t do that, you end up opening a pretty large solution with lot´s of NUnit projects in it.)

    Once you compiled the nunit-console.exe test runner, copy it to the NUnit V2.1\bin directory.

    Now, if you start up your VS.NET project again, the test runner should come up fine, but also show its test results in the output pane of VS.NET. And if you click on one of the failure lines you´ll be taken to the source code line it was caused by.

    Happy unit testing!

    Read more...

  • Three ADO.NET ObjectSpaces Newbie Problems Solved

    When I started playing with Whidbey´s ADO.NET ObjectSpaces object persistence technology (see [1] for an introductory article) I immediately ran into two nasty problems costing me a long time to solve:

    1. Trying to avoid problems I started with a sample from Microsoft. But it won´t run at all. Instead it threw the exception "Can not load System.Data.Objectspaces.dll". I was very surprised, since Whidbey otherwise installed ok. So I thought, maybe the wrong versions were referenced in the Microsoft sample project. But that wasn´t the case. Then some newsgroup postings suggested to use absolute paths in the mapping schema file for the OSD/RSD references. This didn´t help either.
    Finally the unlikely - or very likely - solution was, to just register System.Data.Objectspaces.dll in the GAC, since it turned out, it hadn´t been registered during installation.
    Why this wasn´t done beats me. And I wonder, why only one source in some newsgroup in a slavic language I don´t speak recommended to check, if the assembly had been registered in the GAC.

    2. After I had solved the first problem I was optimistic to now be able to really dive down into ObjectSpaces. But when running my VB.NET console app I again wasn´t able to get past the second line of code:

    Sub Main()
        Dim conn As New SqlConnection("data source=localhost;database=northwind;integrated security=true")
        Dim os As New ObjectSpace("map.xml", conn)
    End Sub

    Instanciation of ObjectSpace now failed with the error message "Cannot locate a domain structure for Customer". (Customer being my sample class.) I again tried to put in absolute paths to the mapping files and add namespaces to class names. Nothing helped. Then I moved the type definition from the library project in my solution to the main project - and the error message at least changed. Then I added a namespace to the class name only in the mapping schema file, and the error message changed again.

    What that means is: ObjectSpaces requires all types referenced by mapping schemas to be already in memory when it gets instanciated. This is of course no problem in the usual Microsoft demo scenarios, where everything is put into just one project. But real solutions consist of many projects, and class definitions for persistent objects mostly will not be located where ObjectSpaces are created.

    So the solution is simple: Either put everything in just one project, or load all assemblies containing types needed by OS mapping schemas before instanciating an ObjectSpace. The simple solution for me was to first instanciate an object of the only type I needed in my main program:

    Sub Main()
        Dim x As New ClassLibrary1.Customer
        Dim conn As New SqlConnection("data source=localhost;database=northwind;integrated security=true")
        Dim os As New ObjectSpace("map.xml", conn)
    End Sub

    3. When I switched from my console application to a winforms app, the above code did not work anymore. The mapping file map.xml could not be loaded. I tried to prefix the filename with the path from Environment.CurrentDirectory - but to no avail. CurrentDirectory does not point to the bin or bin\debug directory of a winforms project during development time. Instead it points to the solution root directory. So I used instead:

    IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly.CodeBase)

    But this did not solve the problem, but just moved it. Now the mapping files referenced my by mapping schema could not be found. So I finally needed to make the paths in the Location attributes absolute:

    <m:Schema Location="c:\...\bin\osd.xml" />

    Resources
    [1] Jan Tielens, Getting Started with ObjectSpaces

    Read more...

  • Enum login time of users on Windows machine to check for logged on user

    In my previous posting ("Check for logged on user on Windows machine") I showed a WMI-based method to check, if a certain user was logged on. Andreas Häber then suggested in a comment, to use the Win32 API function NetUserEnum instead. Since WMI always sounds to me like a heavyweight technology, I liked the idea to resort to "simple" Win32 API calls to solve the problem. Below you find the result of my attempt wrapped in a VB.NET module.

    Usage:

    Call GetUsersLastLogOn() to retrieve an array of all users of the machine running the code. The array contains UserLastLogOn objects providing a username and the date/time of the user´s last logon (lastLoggedOn).

    Dim users() As LogOnInfo.UserLastLogOn
    users = LogOnInfo.GetUsersLastLogOn()
    For Each user As LogOnInfo.UserLastLogOn In users
        Console.WriteLine(user.username & ", " & user.lastLoggedOn)
    Next

    To solve the problem of a Windows service checking for logged on users you need to compare the logon time of the users to a timestamp (e.g. the start time of the Windows service process (call Process.GetCurrentProcess.StartTime())).

    However, although a user might have logged on after the Windows service started, you never know, if he/she still is logged on. The last_logoff information in the USER_INFO_2 structure is not used by Windows.

    That´s the main reason why I find this solution inferior to the one using WMI. Plus, it cost much more effort to code it (compiling information, translating the USER_INFO_2 structure to VB.NET, finding a way to convert UTC seconds to DateTime (easy, but eluded me for quite some time)).

    How does it work:

    NetUserEnum returns a list of USER_INFO_2 structures containing information about users of a certain machine. Passing nothing as the first parameter to NetUserEnum requests users from the local machine, passing 2 as the second parameter requests the info to be returned as USER_INFO_2 structures.

    GetUsersLastLogOn() iterates over all USER_INFO_2 structures, extracts the user name and the last_logon time expressed in UTC seconds since 1/1/1970. The time is converted to a DateTime structure with a simple calculation: add the seconds to midnight of 1/1/1970. The extracted information is wrapped in UserLastLogOn objects and returned as an array.

    Limitation:

    NetUserEnum returns several USER_INFO_2 structures with each call. Calling it once on my system thus is enough. On machines with far more user accounts, though, it might be necessary to call the function several times. However, I did not test that scenario.

    Code:

    Imports System.Management
    Imports System.Runtime.InteropServices

    Module LogOnInfo
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
        Friend Structure USER_INFO_2
            Public name As String
            Public password As String
            Public password_age As Integer
            Public priv As Integer
            Public home_dir As String
            Public comment As String
            Public flags As Integer
            Public script_path As String
            Public auth_flags As Integer
            Public full_name As String
            Public usr_comment As String
            Public param As String
            Public workstations As String
            Public last_logon As Integer
            Public last_logoff As Integer
            Public acct_expires As Integer
            Public max_storage As Integer
            Public units_per_week As Integer
            Public logon_hours As Byte
            Public bad_pw_count As Integer
            Public num_logons As Integer
            Public logon_server As String
            Public country_code As Integer
            Public code_page As Integer
        End Structure


        Public Declare Function NetUserEnum Lib "Netapi32.dll" ( _
            <MarshalAs(UnmanagedType.LPWStr)> ByVal servername As String, _
            ByVal level As Integer, _
            ByVal filter As Integer, _
            ByRef bufptr As IntPtr, _
            ByVal prefmaxlen As Integer, _
            ByRef entriesread As Integer, _
            ByRef totalentries As Integer, _
            ByRef resume_handle As Integer) As Integer

        Public Declare Function NetApiBufferFree Lib "Netapi32.dll" (ByVal buffer As IntPtr) As Integer


        Public Class UserLastLogOn
            Private _username As String
            Private _lastLoggedOn As DateTime

            Friend Sub New(ByVal ui As USER_INFO_2)
                _username = ui.name
                _lastLoggedOn = #1/1/1970#.AddSeconds(ui.last_logon)
            End Sub

            Public ReadOnly Property username() As String
                Get
                    Return _username
                End Get
            End Property

            Public ReadOnly Property lastLoggedOn() As DateTime
                Get
                    Return _lastLoggedOn
                End Get
            End Property
        End Class

     

        Public Function GetUsersLastLogOn() As UserLastLogOn()
            Dim users As New ArrayList

            Dim entriesRead, totalEntries, hResume As Integer
            Dim bufPtr As IntPtr

            NetUserEnum(Nothing, 2, 2, bufPtr, -1, entriesRead, totalEntries, hResume)
            If entriesRead > 0 Then
                Dim iter As IntPtr = bufPtr
                For i As Integer = 0 To entriesRead - 1
                    users.Add(New UserLastLogOn(Marshal.PtrToStructure(iter, GetType(USER_INFO_2))))
                    iter = New IntPtr(iter.ToInt32 + Marshal.SizeOf(GetType(USER_INFO_2)))
                Next
            End If
            NetApiBufferFree(bufPtr)

            Return users.ToArray(GetType(UserLastLogOn))
        End Function


        Public Function IsUserLoggedOn(ByVal userName As String) As Boolean
            Dim mc As New ManagementClass("Win32_Process")
            Dim moc As ManagementObjectCollection = mc.GetInstances
            Dim mo As ManagementObject
            For Each mo In moc
                Dim p As New ROOT.CIMV2.Process(mo)

                Dim processDomain, processUser As String
                p.GetOwner(processDomain, processUser)
                If processUser = userName Then
                    Return True
                End If
            Next
        End Function
    End Module

    Resources:

    -Documentation of NetUserEnum Win32 API function
    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/netmgmt/netmgmt/netuserenum.asp

    -Documentation of USER_INFO_2 structure:
    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/netmgmt/netmgmt/user_info_2_str.asp

    Read more...

  • Check for logged on user on Windows machine

    Motivated by a posting in a user group I tried to find the answer to the question: How can a Windows Service find out, if a user has already logged onto the machine it is running on?

    I googled for a while and it seems, there is no simple function like "IsUserLoggedOn" in the Win32 API or in the .NET Fx base class library. But I stumbled across a hint which pointed into the direction of WMI as a solution.

    So here is my VB.NET version of the function IsUserLoggedOn(). It gets a list of all processes on a machine from WMI and asks each one for the name of the user it is running under. So if a Windows Service is checking for a certain user, e.g. John Doe, if he has already logged on, it just needs to call the function periodically. (If instead it wants to check if any user has logged on, it needs to see, if processes exist running under a non-system account.)

    Imports System.Management

    Module Module1
        Sub Main()
            Console.WriteLine(IsUserLoggedOn("John Doe"))
        End Sub


        Private Function IsUserLoggedOn(ByVal userName As String) As Boolean
            Dim mc As New ManagementClass("Win32_Process")
            Dim moc As ManagementObjectCollection = mc.GetInstances
            Dim mo As ManagementObject
            For Each mo In moc
                Dim p As New ROOT.CIMV2.Process(mo)

                Dim processDomain, processUser As String
                p.GetOwner(processDomain, processUser)
                If processUser = userName Then
                    Return True
                End If
            Next
        End Function
    End Module

    For this code to work you need to reference the WMI library assembly System.Management in your project. Then download the WMI Server Explorer Add-In for VS.NET 2003 (see resources below) and create a managed class (ROOT.CIMV2.Process) for the Processes node in the Management Classes branch of the server explorer. Include the above function IsUserLoggedOn() and you´re set.

    Resources:

    -A introductory article on WMI:
    http://msdn.microsoft.com/msdnmag/issues/02/05/WMIMan/default.aspx

    -Download link for WMI Server Explorer Add-In for VS.NET 2003
    http://www.microsoft.com/downloads/details.aspx?familyid=62d91a63-1253-4ea6-8599-68fb3ef77de1&displaylang=en

     

    Read more...

  • Do the Chameleon - Windows Impersonation Made Easy

    Recently I again stumbled into a situation where I just wanted to test, if a certain portion of my code would also work, if it ran under different Windows user accounts. But instead of switching users by hand, I wanted to impersonate them in my code. How to do this in Managed Code (e.g. C#), is described in quite a few online sources. Here are two examples:

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemSecurityPrincipalWindowsIdentityClassImpersonateTopic.asp

    http://www.codeproject.com/csharp/lsadotnet.asp

    But what I find deplorable is, there are so few (or even no) copy&paste solutions for such small solutions. What I mean is: Authors mostly just explain the problem and a possible solution (with every little step) - but they don´t provide a ready to use component. Why don´t they put everything needed into just a class? Or if it becomes a little more complex, an assembly? It´s this extra level in abstraction or value, that I´m often missing from explanations found online or offline. For understanding I´m interested in every detail - but for my solution I just need a couple of lines explaining, how to use the solution.

    (Before anyone get´s anry at me for my limited world view: Sure there are also many authors who not just think "explanation" but also "added value" and "component". But, alas, I can´t help thinking, it´s still too few.)

    Never mind.

    Since I badly needed a solution for the impersonation problem, I banged together a small "component" myself from what I found online. (Mainly from the Microsoft source.) So whoever is in the need for quick impersonation, here´s a simple VB.NET class to use. Just put it in your project and become a chameleon in just two lines of code:

    Dim aa As New AliasAccount("mydomain\myusername", "mypassword")
    aa.BeginImpersonation()
    ...
    aa.EndImpersonation()

    (See how short an explanation of how to do impersonation can be?)

    It worked find for me within WebForms code behind and WinForms applications.

    Enjoy!

    PS: And here the length details. Read someplace else, what it all means. If you want to use it, just copy the class into your project and you´re set :-)

     

    Public Class AliasAccount
        Private _username, _password, _domainname As String

        Private _tokenHandle As New IntPtr(0)
        Private _dupeTokenHandle As New IntPtr(0)
        Private _impersonatedUser As System.Security.Principal.WindowsImpersonationContext


        Public Sub New(ByVal username As String, ByVal password As String)
            Dim nameparts() As String = username.Split("\")
            If nameparts.Length > 1 Then
                _domainname = nameparts(0)
                _username = nameparts(1)
            Else
                _username = username
            End If
            _password = password
        End Sub

        Public Sub New(ByVal username As String, ByVal password As String, ByVal domainname As String)
            _username = username
            _password = password
            _domainname = domainname
        End Sub


        Public Sub BeginImpersonation()
            Const LOGON32_PROVIDER_DEFAULT As Integer = 0
            Const LOGON32_LOGON_INTERACTIVE As Integer = 2
            Const SecurityImpersonation As Integer = 2

            Dim win32ErrorNumber As Integer

            _tokenHandle = IntPtr.Zero
            _dupeTokenHandle = IntPtr.Zero

            If Not LogonUser(_username, _domainname, _password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, _tokenHandle) Then
                win32ErrorNumber = System.Runtime.InteropServices.Marshal.GetLastWin32Error()
                Throw New ImpersonationException(win32ErrorNumber, GetErrorMessage(win32ErrorNumber), _username, _domainname)
            End If

            If Not DuplicateToken(_tokenHandle, SecurityImpersonation, _dupeTokenHandle) Then
                win32ErrorNumber = System.Runtime.InteropServices.Marshal.GetLastWin32Error()

                CloseHandle(_tokenHandle)
                Throw New ImpersonationException(win32ErrorNumber, "Unable to duplicate token!", _username, _domainname)
            End If

            Dim newId As New System.Security.Principal.WindowsIdentity(_dupeTokenHandle)
            _impersonatedUser = newId.Impersonate()
        End Sub


        Public Sub EndImpersonation()
            If Not _impersonatedUser Is Nothing Then
                _impersonatedUser.Undo()
                _impersonatedUser = Nothing

                If Not System.IntPtr.op_Equality(_tokenHandle, IntPtr.Zero) Then
                    CloseHandle(_tokenHandle)
                End If
                If Not System.IntPtr.op_Equality(_dupeTokenHandle, IntPtr.Zero) Then
                    CloseHandle(_dupeTokenHandle)
                End If
            End If
        End Sub


        Public ReadOnly Property username() As String
            Get
                Return _username
            End Get
        End Property

        Public ReadOnly Property domainname() As String
            Get
                Return _domainname
            End Get
        End Property


        Public ReadOnly Property currentWindowsUsername() As String
            Get
                Return System.Security.Principal.WindowsIdentity.GetCurrent().Name
            End Get
        End Property


    #Region "Exception Class"
        Public Class ImpersonationException
            Inherits System.Exception

            Public ReadOnly win32ErrorNumber As Integer

            Public Sub New(ByVal win32ErrorNumber As Integer, ByVal msg As String, ByVal username As String, ByVal domainname As String)
                MyBase.New(String.Format("Impersonation of {1}\{0} failed! [{2}] {3}", username, domainname, win32ErrorNumber, msg))
                Me.win32ErrorNumber = win32ErrorNumber
            End Sub
        End Class
    #End Region


    #Region "External Declarations and Helpers"
        Private Declare Auto Function LogonUser Lib "advapi32.dll" (ByVal lpszUsername As [String], _
                ByVal lpszDomain As [String], ByVal lpszPassword As [String], _
                ByVal dwLogonType As Integer, ByVal dwLogonProvider As Integer, _
                ByRef phToken As IntPtr) As Boolean


        Private Declare Auto Function DuplicateToken Lib "advapi32.dll" (ByVal ExistingTokenHandle As IntPtr, _
                    ByVal SECURITY_IMPERSONATION_LEVEL As Integer, _
                    ByRef DuplicateTokenHandle As IntPtr) As Boolean


        Private Declare Auto Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Boolean


        <System.Runtime.InteropServices.DllImport("kernel32.dll")> _
        Private Shared Function FormatMessage(ByVal dwFlags As Integer, ByRef lpSource As IntPtr, _
                ByVal dwMessageId As Integer, ByVal dwLanguageId As Integer, ByRef lpBuffer As [String], _
                ByVal nSize As Integer, ByRef Arguments As IntPtr) As Integer
        End Function


        Private Function GetErrorMessage(ByVal errorCode As Integer) As String
            Dim FORMAT_MESSAGE_ALLOCATE_BUFFER As Integer = &H100
            Dim FORMAT_MESSAGE_IGNORE_INSERTS As Integer = &H200
            Dim FORMAT_MESSAGE_FROM_SYSTEM As Integer = &H1000

            Dim messageSize As Integer = 255
            Dim lpMsgBuf As String
            Dim dwFlags As Integer = FORMAT_MESSAGE_ALLOCATE_BUFFER Or FORMAT_MESSAGE_FROM_SYSTEM Or FORMAT_MESSAGE_IGNORE_INSERTS

            Dim ptrlpSource As IntPtr = IntPtr.Zero
            Dim prtArguments As IntPtr = IntPtr.Zero

            Dim retVal As Integer = FormatMessage(dwFlags, ptrlpSource, errorCode, 0, lpMsgBuf, messageSize, prtArguments)
            If 0 = retVal Then
                Throw New System.Exception("Failed to format message for error code " + errorCode.ToString() + ". ")
            End If

            Return lpMsgBuf
        End Function

    #End Region

    End Class

     

    Read more...