Welcome to Neudesic Blogs Sign in | Join | Help
This is the PP slide deck and the code samples from the PFX presentation.
This is the PP slide deck and the source code that uses NUnit.

Running Windows Installer packages that are major upgrade (changing product version and product code) against the assemblies placed in the GAC en the assemblies version is not changed, will result in deleting assemblies without re-installing them. See http://support.microsoft.com/kb/905238 for details.

The following conditions must be met to observe the issue:

1)       Create the Visual Studio Setup and Deployment project that places target assembly ((assemblies) into GAC

2)       Set RemovePreviousVersion property of the Setup project to true.

3)       Compile MSI package and run it once (e.g. with default product version 1.0.0)

4)       Increase the product version (e.g. to 2.0.0). This will result in changing the product code. Run the MSI package.

5)       The previously installed into GAC assembly (assemblies) will be removed after running step 4.

 

To prevent this behavior one of 3 approaches should be followed:

1)       Increase the AssemblyVersion attribute of the assembly that you are upgrading the GAC. This must used only if you have reasons to change the version (changes to functionality or bug fixes) as it may break the dependent assemblies

2)       Use a Windows Installer table-authoring tool to change the sequencing of the RemoveExistingProducts action in the InstallExecuteSequence table to occur after the InstallFinalize action. For example, use the Orca.exe database table editor for creating or editing Windows Installer packages. Orca is available as a part of the Platform SDK for Windows 2003 Server R2 (http://www.microsoft.com/downloads/details.aspx?FamilyId=0BAF2B35-C656-4969-ACE8-E4C0C0716ADB&displaylang=en)

3)       Use the Windows Installer API (through the script or PInvoke) to change the sequence of RemoveExistingProducts and InstallFinalize in InstallExecuteSequence table. The sample script is presented below. This script must be executed in the post-build event of the setup project against the targeted MSI file. Also I would recommend the good introduction to Windows Installer posted in the attached whitepaper.

===============================================================

Option Explicit

If (Wscript.Arguments.Count < 1) Then
Wscript.Echo "Windows Installer utility to execute SQL queries against an installer database." &_
vbLf & " The 1st argument specifies the path to the MSI database, relative or full path"
Wscript.Quit 1
End If

Dim openMode : openMode = 1 'msiOpenDatabaseModeTransact

On Error Resume Next

Dim installer : Set installer = Wscript.CreateObject("WindowsInstaller.Installer") : CheckError
' Open database
Dim database : Set database = installer.OpenDatabase(Wscript.Arguments(0), openMode) : CheckError

Wscript.Echo "Reading InstallFinalize sequence #..."

Dim query, record, rowData
query = "SELECT `Sequence` FROM `InstallExecuteSequence` WHERE `Action` = 'InstallFinalize'"
Dim view : Set view = database.OpenView(query) : CheckError
view.Execute : CheckError
Set record = view.Fetch
If record Is Nothing Then
 Wscript.Echo "InstallFinalize sequence # cannot be found."
 Wscript.Quit 1
Else
 rowData = record.IntegerData(1)
End If

Wscript.Echo "InstallFinalize sequence # = " & rowData

Wscript.Echo "Changing RemoveExistingProducts sequence to be last action..."

query = "UPDATE `InstallExecuteSequence` SET `Sequence` = " & rowData + 100 & " WHERE `Action` = 'RemoveExistingProducts' "
Set view = database.OpenView(query) : CheckError
view.Execute : CheckError
database.Commit
Wscript.Echo "Done."
Wscript.Quit 0

Sub CheckError
Dim message, errRec
If Err = 0 Then Exit Sub
message = Err.Source & " " & Hex(Err) & ": " & Err.Description
If Not installer Is Nothing Then
Set errRec = installer.LastErrorRecord
If Not errRec Is Nothing Then message = message & vbLf & errRec.FormatText
End If
Wscript.Echo message
Wscript.Quit 2
End Sub

Since I am doing a great of deal of work on the architecture, standards and guidelines, the Code Style Enforcer attracted me as an alternative way to manage the source code proactively vs. checkin policies that can be used with VSTS/TFS.

The brief description of the tool:

Code Style Enforcer is a DXCore plug-in for Visual Studio that checks the code against a configurable code standard and best practices. It is developed for C#, but some of the rules will also work for VB .NET, though not tested. The code standard is currently configurable with the following rules:

• Name rules, where it is possible to have different rules for different members, i.e. fields, constants, variables, methods, properties and so on.
• Visibility rules, where you can specify the visibility that is valid for different members. It is also possible to specify that the visibility has to be specified explicitly.
• Implementation rules, where it is possible to specify that interface implementations are to be made explicitly or implicitly.

The default rules are based on the C# Coding Standard from IDesign. It is the most complete code standard available and it is free to download.

The download is avaialble here: http://joel.fjorden.se/static.php?page=CodeStyleEnforcer.
Too bad there is no source code (maybe the author is considering the commercial version of it in the future).

Recently I was converting the COM+ app component to WCF service. There were couple of chan ges required specicially regarding the interface type of programming that is enforced by COM+.

E.g. if you have the data classes passed as input or returned as output parameters, you need to use interfaces vs class type declarations. In such a way a collection of objects of custom class will be passed back as ICollection. COM+ does not understand generics, so strongly typed collections cannot be used.

When you convert COM+ app component classes to WCF, you naturally exclude the interfaces for the custom data classes and present them as DataConstracts (classes marked as data contracts with datamember). If you forget to change the return type ICollection to the actual implementation (e.g. List or List<T>; the latter is preferred), WCF will not complain and you will be able to genarate and use the proxy on the client app until you try to call the method that return collection. Serializartion exception will be thrown as WCF does not allow to pass interaces as the results.

The short story story is:

public ICollecton GetResults()

must be converted to

public List<T> GetResults(), where T is the actual type.

On the client side, this function will be nicely avaialble as the array of T (T[]).

Recently I was working for the customer on the proof of concept for N-tier application where the client was using IE to authenticate itself against the web tier that talks to the app tier which talks to SQL Server. It sounds like an relatively easy task even in .NET 1.1, but the web tier had to be load balanced and the app tier had to be load balanced and both tiers are running on physically separate boxes. Plus the original caller identity had to flow from IE to IIS and to the app server for authorization purposes. All involved parties are configured in Windows domain with Active Directory. The restriction is that you cannot run IIS as the hosting environment on the app tier for security reasons (I know it may not be reasonable, but this is the mandatory infrastructure requirement). The oviy nature aous choices prior to .NET 3.0 were COM+/Enterpsie Services, NET Remoting or ASMX web services. NET Remoting is insecure by nature and is not a good migration path to future communication platform, ASMX service cannot run w/o IIS.

So COM+/Enterpise Services had to examine. It works as expected with one caveat: you need to setup the Kerberos delegation to allow the caller identity to flow from the IE to IIS to the middle tier. Kerberos is required to authenticate services (IIS and app tier) and it is the prerequisite for the delegation. This solution works pretty well with single IIS and app tier servers.

If you add the load balancer to the web tier (hardware load balancer or NLB) you can still make the Kerberos working (see http://www.microsoft.com/technet/prodtechnol/windowsserver2003/technologies/security/kerbnlb.mspx). The whole trick here is that you are registering the service principal name (SPN) for the domain user account under with the IIS application poll runs for all IIS services (e.g. setspn –a http/<serverN> domain\user) and the load balancer name (e.g. setspn –a http/<nlb> domain\user). It means that the IE client hits the load balancer <nlb> and receives the response from one of the <serverX>. There are special requirements how to enable IIS to use Kerberos authentication (see http://support.microsoft.com/?id=215383).

If you add the load balancer to the app tier (again it can be a hardware or software solution), the Kerberos delegation stops working with COM+ being unable to do mutual authentication against the load balancer name. The problem is that most COM+-related services (e.g. remote procedure call) are running under the NT Authority\Local System account which maps to the Domain Computer account for all external connection. the Making the same configuration for NLB-Kerberos, but for COM+ proved to be impossible as there is no way to use the custom domain user account for the COM+-related services. Yes, you have already heard it: DO not change the accounts under which the RPCSS et al runs as it will break the Windows OS. So if you are stuck with COM+-only, your only way to resolve the issue is to configure the COM+ proxies at the web tier servers to use the individual app tier servers (NOT the app tier <nlb>). You have good utilization of the web tier resources, but somewhat questionable utilization of app tier resources.

Here is when WCF comes handy. WCF offers more flexibility for hosting the services:

·        Self-hosting (e.g. WinForms, console app)

·        IIS 5.0/6.0 or WAS/IIS 7.0 in Vista

·        Windows/NT Service

Using console application is not a good choice for the app tier. Running IIS 5.0/6.0 is not possible because of the infrastructure constraints. Windows Activation Services (WAS) are coming only with Vista and we cannot wait for it. So the natural choice was left for Windows Service. It offers automatic startup and RUNNING UNDER THE SPECIAL DOMAIN ACCOUNT. All you have to do is to start the WCF service inside the Windows/NT Service, configure it to use one the bindings and enable impersonation on the client or service WCF configuration. Below is the detailed explanation how it can be done:

The custom NT Service class:

    public class Service : ServiceBase

    {

        private ServiceHost serviceHost = null;

 

        protected override void OnStart(string[] args)

        {

            if (serviceHost != null)

            {

                serviceHost.Close();

            }

 

            serviceHost = new ServiceHost(typeof(WCFService));

 

            serviceHost.Open();

        }

 

        protected override void OnStop()

        {

            if (serviceHost != null)

            {

                serviceHost.Close();

                serviceHost = null;

            }

        }

      }

The application configuration file is not different from the one that you can find the SDK samples:

 

<system.serviceModel>

    <bindings>

      <netTcpBinding>

        <binding name="tcpBinding" />

      </netTcpBinding>

    </bindings>

    <services>

      <service behaviorConfiguration="MyServiceBehavior" name="Esps.BusinessObjects.RegionManager">

        <endpoint address="mex" binding="mexTcpBinding" bindingConfiguration=""

          name="Mex" contract="IMetadataExchange" />

        <endpoint binding="netTcpBinding" bindingConfiguration="tcpBinding"

          name="Tcp" contract="IWCFService" />

        <host>

          <baseAddresses>

               <add baseAddress="net.tcp://localhost:8001/service" />

          </baseAddresses>

        </host>

      </service>

    </services>

 

    <!--For debugging purposes set the includeExceptionDetailInFaults attribute to true-->

    <behaviors>

      <serviceBehaviors>

        <behavior name="MyServiceBehavior">

          <serviceMetadata httpGetEnabled="true" />

          <serviceDebug httpsHelpPageEnabled="false" includeExceptionDetailInFaults="true" />

        </behavior>

      </serviceBehaviors>

    </behaviors>

  </system.serviceModel>

 

When you install the Windows Service, you need to set the account under which it runs to the selected domain user account (e.g. Domain\MyServiceAccount).

 

The WCF client configuration which you setup at each web application will need to contain the following information:

 

<bindings>

   <netTcpBinding>

    <binding name="tcpBinding" />

   </netTcpBinding>

  </bindings>

            <client>

   <endpoint address="net.tcp://nlbFQDN:8001/service" binding="netTcpBinding"

    bindingConfiguration="tcpBinding" contract="IRegionManager"

    name="IRegionManager">

     <identity>

       <servicePrincipalName value="MYSERVICE/NLB"/>

     </identity>

   </endpoint>

 

What is servicePrincipalName and what is MYSERVICE/NLB? See the infrastructure setup below:

1)    Add Domain\MyServiceAccount to the Active Directory

2)    Mark this account as trusted for delegation (see Account tab or Delegation tab in the AD User configuration)

3)    Add the SPN for the WCF hosting service for each machine where you are installing the service:

Setspn –a myservice/<serverN>:8001 Domain\MyServiceAccount

Setspn –a myservice/<serverN-FQDN>:8001 Domain\MyServiceAccount

4)    Add the SPN for WCF hosting service for the app tier NLB name:

Setspn –a myservice/<nlb>:8001 Domain\MyServiceAccount

Setspn –a myservice/<nlb-FQDN>:8001 Domain\MyServiceAccount

5)    Use the service and the app tier.

 

Note 1: SPN is built in the form servicename/<servername>:<portnumber>. <servicename> can be any string that uniquely identifies your service in your domain except for the well-known SPNs (host, dcom, http etc.). If you register SPN using the well known SPN, you will end up with duplicate SPN which breaks Kerberos authentication. Finding the duplicates can be done by searching the Active Directory. <portnumber> is the port number that your binding is configured for.

 

Note 2: In the previous examples I used FQDN as acronym for “Fully-Qualified Domain Name”. It is important to use it instead of the short NETBIOS name to make Kerberos working.

 

Note 3: IE client must be configured to use Integrated Security and the domain name where the web tier load balancer is located must be added to the Local Intranet or Trusted Sites section. The simple ASP page can be used to verify the protocol used to authenticate the IE client against IIS (see the listing below). If the Kerberos authentication chain is broken between IE and IIS, it defaults to NTLM which cannot be used to hop more than one time and disables the services mutual authentication.

 

Note 4: That said, there are only 2 products out of the box that can do Kerberos delegation easily and work in NLB environment. They are IIS and SQL Server. But you can easily make your application working in the same manner using WCF and Windows Service. Of course, if you can host the WCF service in IIS, this is also achieved easily and you gives you additional benefits of automatic activation and monitoring.

 

Note 5: In Windows 2003 functional level you can configure the Kerberos to work only with specific services. This is called Kerberos constrained delegation. See details here: http://technet2.microsoft.com/WindowsServer/en/library/c312ba01-318f-46ca-990e-a597f3c294eb1033.mspx?mfr=true.

 

I've just watched the web cast on System.Transactions in .NET 2.0 and I highly recommend to review it for anybody interested in how to make it easier to work with transactions nowdays. The webcast reviews the classes in this namespace, using different resource managers (RMs): SQL Server 2005 and SQL Server 2000, discusses the lightweight transactions (specific for SQL2K5 and MSMQ) and MS DTC. Plus it gives the quick intro how to write your own RM (e.g. file manager) by implementing required interfaces. The file manager sample will be an overkill in Vista SP1/Longhorn as the new TxF (transactional file system) will automatically provide the RM to handle the operations with NTFS objects in a transactional manner. 

The web cast is available via http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032295238&EventCategory=5&culture=en-US&CountryCode=US

Data Access Application Block (DAAB) has very nice features. Parameter discovery and caching is one of them. What it does is when the the client code creates a DbCommand object is constructed from the name of the stored procedure and the list of parameters as an array of objects, DAAB code goes to the database, discovers the stored proc parameters (types, names, direction etc.) and caches them in the simple hash-table which is defined in the base Database class. If the stored procedure parameters information has been previously cached in DAAB, then the list of parameters is retrieved from the cache and the actual values provided in array of the objects are assigned and the DbCommand is returned to the client code for further using (executing the dataset, scalar, NonQuery, XML query etc.).

The parameter cache is indexed by the name of the stored procedure and the connection string. DAAB checks the the number of parameter values provided by the client code and the actual number of parameters cached in the object of the Database class (actually one of its descendants since Database class is an abstract class) and throws an exception if they do not match.

Everything works fine if the client code executes the methods without assuming that the stored procedure declaration can be changed. If it happens during the run-time, the next time you try execute it, DAAB will throw the exception even the client code provides the correct number of parameter values in the array of objects. This is a valid case if the DAAB is used in the middle-tier and passes the calls from the data access to the database. At any moment of time the definition of the stored procedure is changed, the client code (Data Access or Business Objects assembly methods) are updated properly to match the stored procedure, but you may still find yourself in situation when the DAAB parameter cache is out of sync and must be refreshed. 

To do so, the client code must catch the exception, analyse it and clear the cache by calling the <Database>.ClearParameterCache method which throw away ALL cached entries. This may not be an ideal solution and you would like to encapsulate the cache cleanup functionality inside the DAAB and make it more precise to clear the cache specifically for tbe failed set of cached parameters. Unfortunately, the only way to make it centralized is to customize the Database class of DAAB and make this feature available to all descendant classes.

Here are the steps to customize the DAAB code

1) ParameterCache Class: Add overloaded method to clean the parameter cache for the given DbCommand Database Class

2) Database class:

2.1) Modify/add new methods that are performing the actual work against the database:  

  • DoCustomLoadDataSet(DbCommand command
  •  DoCustomExecuteNonQuery(DbCommand command
  • DoCustomExecuteScalar (DbCommand command
  • DoCustomExecuteReader(DbCommand command)

Modification details

The following pseudo-code snippet presents the modifications to be implemented. The modification lines are:

try
{
 Execute original method
}
catch (DbException ex)
{
 if (ex is related to command parameters)
       Clean parameter cache for the command
        Prepare the command
  Execute original method
 else
        throw; 
}

2.2.) Add method bool IsParameterRelated(DbException exception): This method will determine if the exception is related to the parameters.

The descendant classes will override the IsParameterRelated method to analyze the exception. So for SQL Server there are two appoaches:

3)  Detecting the parameter related error by analyzing the state of objects involved in the operations.

  • Analyze DbCommand object:  The object must be of StoredProcedure type
  • Analyze DbCommand.Parameters collection: The collection must contain at least one input, output or input/output parameter
  • Analyze DbCommand.Connection object: The object must be initialized and the connection string must contain value. The connection state must be open. The closed connection state indicates the severe error on the SQL Server side. If the error is of severity greater than 20, the connection will be closed by SQL Server.
  • Analyze if the DbCommand parameters were cached previously: If the parameters for the DbCommand were cached previously, then the most recent execution of the command was successful. The failure of new command execution may be related to the changes in the parameter definition.

Alternatively, you may want to check the Number property of SqlException object and detect if is related to stored procedure definitions. This approach is not practical as you coupling your solution with particular version of SQL Server engine.

The interesting thing is that there are certain cases that do not throw any exception unless the application that uses DAAB is restarted. For example, changing the types of the stored procedure parameter may be leveraged by SqlDataAdapter as long as there is no range violation between the value and the actual parameter definition. What it means is that the DAAB parameter cache will be OK as long as the underlying ADO.NET Data Adapter can bind the parameters.

Here is some sample code related to this issue:

private object DoCustomExecuteScalar(DbCommand command)
        {
            try
            {
                return DoExecuteScalar(command);
            }
            catch (DbException e)
            {
                if (IsParameterRelatedException(command, e))
                {
                    using (DbCommand newCommand = GetReinitializedCommand(command))
                    {
                        return DoExecuteScalar(newCommand);
                    }
                }
                else
                    throw;
            }
        }

protected DbCommand GetReinitializedCommand(DbCommand oldCommand)
       {
            if (oldCommand.CommandType != CommandType.StoredProcedure)
                throw new ArgumentException(Resources.ExceptionMessageParameterMatchFailure, "oldCommand");

            parameterCache.Clear(oldCommand, this);
           
            // Copy old parameters value
            int parameterIndexShift = UserParametersStartIndex();
            object[] parameterValues = new object[oldCommand.Parameters.Count - parameterIndexShift];
            for (int i = parameterIndexShift; i < oldCommand.Parameters.Count - 1; i++)
                parameterValues[i] = oldCommand.Parameters[i].Value;

            DbCommand newCommand = GetStoredProcCommand(oldCommand.CommandText, parameterValues);
            PrepareCommand(newCommand, oldCommand.Connection);
            return newCommand;
        }

protected virtual bool IsParameterRelatedException(DbCommand command, DbException exception)
        {
            if (command == null) throw new ArgumentNullException("command");
            if (exception == null) throw new ArgumentNullException("exception");

            bool result =
                CheckCommandType(command)
                && CheckCommandConnecton(command)
                && CheckCommandParameters(command);
               
            return result;
        }

What if your managed code is hosted inside the COM+ component and you still need to access the settings similar to what you have in WinForms (app.config) or ASP.NET (web.config ) application?

If your component is configured as a Library Application, then it will use the settings from the hosting application since it is runnig in-proc. If the component is configured as a Server Application, the choices are presented below:

  1. Create/update dllhost.exe.config file (Very bad idea since it will affect all COM+ applications on the machine)
  2. Provide the initialization constructor (Too much effort)
  3. Create custom configuration scheme (Too much effort)
  4. Use the Application Root Directory and manifest file (The easiest way with minimum impact

To go with the last approach you shall need to configure the Application Root Directory setting (avaialble for COM+ 1.5 - Windows XP and Windows Server 2003) and put two files application.manifest and application.config into this directory.

Application.Manifest must contain the following:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
</assembly>

Application.Config is the standard .NET configuration file.

There are different solutions to make it working when you need to access your home or office computer being away from it. Network restrictions may prevent you from using the remote desktop/terminal client or you do not have the VPN connectivity to the corporate network. The solution that worked for is to use the free version of LogMeIn which allows you to control remotely, print, share files etc. using the standard HTTP connection. LogMeIn installs the client Windows service on the computer that you need to control. You can invite the guests and allow them to view the shared application or desktop on your remote computer (similar to webex et al). Check www.logmein.com for the details.

I recently had a problem in the COM Interop when you try to use event sink with parameter of enumeration type exposed to VC++ 6.0 ATL. This event sink is working perfectly well with VB6, but not with VC++ 6.0.

We are getting the callback made into C++ with no problem. The problem occurs when we try to call a method of our EventArgs-derived parameter to the event callback. When we make a call to say, get_Name(&bstr); on our EventArgs-derived class in the C++ callback ('Name' is a property in our EventArgs derived class) we get the following error from the VC6 debugger:

"The value of ESP was not properly saved across a function call. This is usually the result of calling a function declared with one calling convention with a function pointer declared with a different calling convention"  

The investigation resulted in the discovery of the bug in ATL that Microsoft will not fix :-). But here is a description and workaround to make it working

BUG: IDispEventImpl Event Handlers May Give Strange Values for Parameters
http://support.microsoft.com/?id=241810

FIX: Events Fail in ATL Containers when Enum Used as Event Parameter
http://support.microsoft.com/?id=237771 - This fix worked!

BUG: ATL Events Don't Fire If Defined With Interface Alias
http://support.microsoft.com/?id=244204