Friday, May 15, 2009

Encrypt configuration file in a custom managed action on x64

When I install a .NET (or ASP .NET) application, I like to encrypt the sensitive parts of the configuration file like the connection strings. So I have been used to create a Custom Installer class to achieve this. There are a few tricky things to take into account, like :

  • when your configuration is loaded by the installer, it should be able to resolve embedded dependencies (like Enterprise Library assemblies) even when they are not in the GAC.
  • this class accepts a EXEPATH parameter that contains the path of the application which application file is to be encrypted. It should then be terminated by an extra \ character.

For the record, here is how I do this :

[RunInstaller(true)]
public partial class EncryptConfigInstaller:
Installer
{

public EncryptConfigInstaller()
{
InitializeComponent();
}

public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);

string exePath=Context.Parameters["exepath"].TrimEnd('\\');

AppDomain.CurrentDomain.SetData("EXEDIR", Path.GetDirectoryName(exePath));
AppDomain.CurrentDomain.AssemblyResolve+=_AssemblyResolver;
try
{
EncryptConfiguration(ConfigurationManager.OpenExeConfiguration(exePath));
} finally
{
AppDomain.CurrentDomain.AssemblyResolve-=_AssemblyResolver;
}
}

private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
AssemblyName name=new AssemblyName(args.Name);

return Assembly.LoadFile(Path.Combine((string)AppDomain.CurrentDomain.GetData("EXEDIR"), string.Concat(name.Name, ".dll")));
}

private static void EncryptConfiguration(Configuration config)
{
EncryptSection(config.GetSection("connectionStrings"));

config.Save(ConfigurationSaveMode.Modified);
}

private static void EncryptSection(ConfigurationSection section)
{
if ((section!=null) && (!section.SectionInformation.IsProtected))
{
section.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
section.SectionInformation.ForceSave=true;
}
}

private static ResolveEventHandler _AssemblyResolver=new ResolveEventHandler(CurrentDomain_AssemblyResolve);

}

Then even if I know that managed actions are considered evil, as I do not think I have a choice here, I am using the well known WiX Custom Managed Actions trick. So everything is fine in a perfect world.

Except when a client tried to install one of my applications on a x64 platform (namely Windows Server 2008). Once solved the obvious native libraries problem, I was still puzzled by the fact that the user who had installed the application was the only one able to run it ! It was only after a couple of days of wrong conjectures, poor tricks and unproductive Google searches that I cornered the problem : the encryption process was broken.

Extensive reasons behind that can be found here, but basically it is because InstallUtilLib.dll, which is used to launch Installer classes, is a native library. As such, you should use the 64 bits version on a x64 platform. So I ended up copying both versions in an independent lib folder, and here is the magic corresponding WiX line :

<Binary Id="InstallUtilBinary" SourceFile="$(sys.CURRENTDIR)..\..\lib\$(sys.BUILDARCH)\InstallUtilLib.dll" />

Yes, I learned a lot about x64 lately. But I guess I am not done yet :-)

Oracle Instant Client in Visual Studio

In my previous post, I mentioned the fact that I added the Oracle Instant Client files as Content files in a Visual Studio project. I would like to write more about this here.

If you intend to use your application with Instant Client, you will want to be able to debug it with Instant Client. Which means that the libraries have to be copied along your generated application in the bin\Debug folder. The best way to achieve this is to include then as Content files in your project.

But it gets more tricky if you want to be able to debug it on 32 bits platform as well as on a 64 bits platform : the source code is the same (I am obviously writing about a .NET application, here), you just have to pick the correct library depending on the platform you are debugging on.

My way to do this is :

  • I store the Instant Client libraries in an independent folder, say lib\Oracle\Instant Client.
  • Each platform lies in a dedicated subfolder : x86 for the 32 bits version, x64 for the 64 bits version.
  • I manually tweak the .csproj project file so that it picks the right version depending on the platform I am running Visual Studio on :
    <PropertyGroup>
    <ProcessorArchitecture>x86</ProcessorArchitecture>
    <ProcessorArchitecture Condition=" '$(PROCESSOR_ARCHITECTURE)' == 'AMD64' ">x64</ProcessorArchitecture>
    </PropertyGroup>
    <ItemGroup>
    <Content Include="..\..\lib\Oracle\InstantClient\$(ProcessorArchitecture)\oci.dll">
    <Link>oci.dll</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="..\..\lib\Oracle\InstantClient\$(ProcessorArchitecture)\orannzsbb11.dll">
    <Link>orannzsbb11.dll</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="..\..\lib\Oracle\InstantClient\$(ProcessorArchitecture)\oraociei11.dll">
    <Link>oraociei11.dll</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    </ItemGroup>
    <ItemGroup Condition=" '$(ProcessorArchitecture)' == 'x86' ">
    <Content Include="..\..\lib\Oracle\InstantClient\$(ProcessorArchitecture)\msvcr71.dll">
    <Link>msvcr71.dll</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    </ItemGroup>

imageThis code is based on the PROCESSOR_ARCHITECTURE environment variable. This will be set to x86 on a 32 bits platform, as expected, but to AMD64 on a 64 bits platform. That is why I am using a custom property to get the x64 value back.

I could also simply rename the library subfolder to AMD64, but I like x64 more (I know : I am picky). And besides, it makes my WiX files more straightforward…

Oracle (Not So) Instant Client

Developing database oriented .NET applications is quite a no brainer once you are used to your API (ADO.NET, Enterprise Library Data Application Block…) or your ORM (NHibernate…). Just pick your database vendor ADO .NET provider, which usually consists of one assembly that you distribute with your application, and that’s it. That is how it works with SQL Server (of course), but also TeradataMySQL, PostgreSQL, SQLite… You name it, that is the way it works.

Oracle, you said ? Well, that must be the exception that confirms the rule (along with DB2, but I would like to focus my rant on Oracle, if I may). It works quite like this. You need an ADO .NET provider AND a native client. The problems with this are :

But thanks to the Oracle guys, there is another solution : Oracle Instant Client. It consists of just a few DLLs that you can distribute along with your application and that allow connection to Oracle databases. If you are not too picky about character sets and supported languages, you can trim it down do 19Mb.

I became quite efficient with this : I add the necessary files as Content in my Visual Studio project, and a simple Setup project makes a good enough installer for my solution.

Then this week, a client called me about how one of these applications just crashed after he installed it on a Windows 2008 Server. The fact was : it was a 64 bits Windows. As the .NET application was compiled as AnyCPU, it was automatically launched as a 64 bits application, and then you can guess by yourself what happened when the 32 bits Oracle client was loaded in memory. At this point, my options were :

  • compile the application as x86, so that it would be launched as a 32 bits application even on a x64 platform. But why tweak my application when it is really Oracle that was at fault ?
  • leave the application as AnyCPU, but distribute a different client depending on the platform it is installed on. This is the (more interesting) road I took.

I decided to give up on the standard Setup project (had I any other option ?) and use WiX 3.0 instead. My first attempt was to use a single installer to be used on both platforms (x86 and x64), and distribute the Oracle Instant Client 11 depending on the platform (I found that version 10 made my application crash on Windows Vista x64, while version 11 worked fine…). This is how I achieved this :

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLLOCATION">
<?if $(sys.BUILDARCH) = "x86" ?>
<Component Id="OracleInstantClientFiles_x86" Guid="{AA0076CE-F7B6-4cd8-9B67-199F665A8E77}" KeyPath="yes">
<Condition>
<![CDATA[Installed OR NOT VersionNT64]]>
</Condition>
<File Id="OracleInstantClientFiles_x86_msvcr71.dll" Name="msvcr71.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\msvcr71.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x86_oci.dll" Name="oci.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\oci.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x86_orannzsbb11.dll" Name="orannzsbb11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\orannzsbb11.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x86_oraociei11.dll" Name="oraociei11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\oraociei11.dll" DiskId="1" />
</Component>
<?endif ?>
<Component Id="OracleInstantClientFiles_x64" Guid="{3CCDBDB6-D45A-4523-8CC7-730D3A8851D3}" KeyPath="yes">
<Condition>
<![CDATA[Installed OR VersionNT64]]>
</Condition>
<File Id="OracleInstantClientFiles_x64_oci.dll" Name="oci.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x64\oci.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x64_orannzsbb11.dll" Name="orannzsbb11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x64\orannzsbb11.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x64_oraociei11.dll" Name="oraociei11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x64\oraociei11.dll" DiskId="1" />
</Component>
</DirectoryRef>
</Fragment>

<Fragment>
<ComponentGroup Id="OracleInstantClientFiles">
<?if $(sys.BUILDARCH) = "x86" ?>
<ComponentRef Id="OracleInstantClientFiles_x86" />
<?endif ?>
<ComponentRef Id="OracleInstantClientFiles_x64" />
</ComponentGroup>
</Fragment>

</Wix>

This works alright. Just reference the OracleInstantClientFiles component and there you have it. But I was annoyed by the fact that my application would install in the Program Files (x86) folder by default. So I went with the dual installer solution. And the source code became :

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLLOCATION">
<Component Id="OracleInstantClientFiles" Guid="{AA0076CE-F7B6-4cd8-9B67-199F665A8E77}" KeyPath="yes">
<?if $(sys.BUILDARCH) = "x86" ?>
<File Id="OracleInstantClientFiles_x86_msvcr71.dll" Name="msvcr71.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\msvcr71.dll" DiskId="1" />
<?endif ?>
<File Id="OracleInstantClientFiles_oci.dll" Name="oci.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\$(sys.BUILDARCH)\oci.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_orannzsbb11.dll" Name="orannzsbb11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\$(sys.BUILDARCH)\orannzsbb11.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_oraociei11.dll" Name="oraociei11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\$(sys.BUILDARCH)\oraociei11.dll" DiskId="1" />
</Component>
</DirectoryRef>
</Fragment>

</Wix>

So now I can distribute the correct Oracle files (worth 130Mb per platform) along with my less than 2Mb application !

Wednesday, October 29, 2008

How to install Oslo with SQL Server Express Edition ?

As I was delving into the Visual Studio DSL Tools to design my first DSL for , I came across this new modeling platform by Microsoft : Oslo. In fact, it is so new that the first CTP has just been released.

Haven van OsloAs I already wrote on the blog of Salamanca, the vision for this new tool seems to be exactly what I had been needing :

It's actually taking the kind of models that you're seeing arising in specific domains, like software management in System Center, or your data design over in SQL, or your process activities over in BizTalk and saying, we need to take all these domains and be able to put them into one model space. In fact, we need to let people create their own domains that aren't just isolated, but that exist in this one modeling space. And that's what Oslo is about.

That's it. I just quoted Bill Gates !

So it was unavoidable that I tried to download and install the October 2008 CTP on my computer. Everything was OK, except for the fact that the installer could not create the repository on my SQL Server 2008 Express Edition database. The message was :

Create repository database failed, please try later

So I tried later, as suggested in the installed ReadMe file. Indeed, my SQL Server instance not being the default instance on my system, but rather being a named instance (the name being "SQLEXPRESS"), I understand why the repository creation failed. The trouble is that the command lines included in the file to manually create the repository are incorrect. So here are the correct ones :

CreateRepository.exe /db:.\SQLEXPRESS
mx.exe -i:Models.mx -db:Repository -s:.\SQLEXPRESS

Please note that the second command failed at first with the following message :

There is insufficient system memory in resource pool 'internal' to run this query.

But, as suggested on this Microsoft Connect SQL Server issue, letting the server "sit for a moment" solved the problem...

Monday, August 25, 2008

The road to Salamanca

I haven't been around this blog for quite some time now. Don't take this as another useless and meaningless apology for not sticking to a self assigned schedule. The reason for this short post is just to tell the world that we have decided to turn our software factory into an open source project, hosted on CodePlex : . And there is even a blog attached to it, for which I am the main contributor. So that will be a excellent reason (I hope) for me not to find the time to update this one :-)

Salamanca is a product I really grew found of, and which I think brings great promises, especially in the field of with the concept of activities, that can be thought of as a View-Controller pattern.

Granted, as the size of the observable world seen from this blog is rather small, this post will also be used as a test for the trackback and reactions mechanisms on The road to Salamanca.

Thursday, June 5, 2008

Missing

I got stuck with this error message yesterday :

System.Data.OracleClient requires Oracle client software version 8.1.7 or greater

Mmmh. Let me give you some context. I have been working for almost a year now on this ASP .NET application :

  • connected to an Oracle 10g database.
  • uses the Microsoft ADO .NET provider for Oracle (preferred over ODP .NET 10g for its Visual Studio 2005 integration and its ability to work above Oracle Instant Client).
  • bundled Oracle Instant Client 10.2 (more on that later...).
  • uses impersonation.
  • tested on numerous development environments and a couple of servers without a glitch.

This error occurred on a new and freshly installed Windows Server 2003 server. Without a clue, I looked for help on the Internet, basing my search on the error message. An I found a good one, or so I thought. Let me get this straight : if you get the error above, it is very likely that you have to tweak your security rights on your Oracle client installation as suggested on the previous link. But not in my case, because of the way I had bundled Oracle Instant Client in my application :

  • the DLLs are deployed in the application bin directory.
  • the application bin directory is added in front of the %PATH% environment variable at application startup. This is how it is done :

In the Global.asax file :

So in my case, the Oracle client installation was my application bin directory, which had the proper rights. This did not prevent me from trying to give everyone full access, just to make sure. But, very predictively, it failed to solve my problem.

I began to sweat a little. Did I really need to install a full Oracle client on the server ? No : I proved otherwise. I thought...

At last resort, I decided to use the indispensable Reflector for .NET to check the piece of code responsible for throwing the exception. Nothing very fancy there, but I noticed after a while a peculiar trace mechanism. I tried to analyze it to find a way to enable it. And after a long while, I found out that Microsoft ADO .NET providers could be plugged into the Event Tracing for Windows for debugging. Not very straightforward, but I managed to get it work. And here is the interesting piece of trace I got :

<oc|ERR|THROW> 'System.DllNotFoundException: Impossible de charger la DLL 'oci.dll': Le module spécifié est introuvable. (Exception de HRESULT : 0x8007007E) ,   à System.Data.Common.UnsafeNativeMethods.OCILobCopy2(IntPtr svchp  IntPtr errhp  IntPtr dst_locp  IntPtr src_locp  UInt64 amount  UInt64 dst_offset  UInt64 src_offset) ,   à System.Data.OracleClient.OCI.DetermineClientVersion()'

Please pardon my french ;-) "Come on", I said to myself, "how can you not find this DLL when it is clearly in your %PATH% ?" Or is it not ? To be sure, I checked with Process Explorer. Indeed, it is.

The truth is out there : what if it could find the said DLL, but could not load it for any reason ? Indeed, a search focusing more on DllImport than on Oracle confirmed this : a missing dependency could trigger this exception. So I decided to check dependencies with the Dependency Walker.

image

So the Oracle Instant Client relies on MSVCR71.DLL, which is just not there. Isn't that DLL supposed to be by default in the C:\WINDOWS\system32 directory ? Well, it used to, but not any more.

This is it. This dependency is so common that it had been somehow installed on all previous environments but not this one. I just added it to the application installation packages, alongside OCI.DLL, and that was it.

Note : Oracle Instant Client 11.1 is delivered with all its dependencies, including MSVCR71.DLL...

Wednesday, February 20, 2008

About Visual Studio, SSIS and DSLs

I have had the opportunity to discover Sql Server 2005 Integration Services (SSIS) lately. That was a double blow. At first, I found it highly usable, though rough on the edges : still only a very promising tool. And then I found it was the perfect example of a software factory created with the Visual Studio Domain-Specific Language Tools. It is great to see Microsoft eating its own dog food, and create a great tool with it.

I kept playing working with it for a while and then I slightly became more and more frustrated by this experience. For instance, the process of modifying a data flow is tedious and laborious if a data source structure has been changed. And as happened with the Windows Forms designer, I soon felt the urge to bypass the designer to quickly mess with the code. My deep feeling is that designers are great to get started, editors are great to get finished. But in the case of SSIS, there is no such thing as code. All you can have is a XML file containing your flow design, messed up with presentation concerns, useless metadata, magical numbers... Great for machines, useless for humans.

The problem here, is that you have no Domain-Specific Language (DSL) per se. Or, better, that the DSL has been generated by a machine. Some people have identified this flaw and felt the need for proper code, even for ETLs. I have even found a code only ETL being developed. This, in my opinion, is the right thing to do, and I shall remember it when I design my own Software Factories : a DSL is mandatory, the designer is here for comfort.

SSIS is a great tool. It just lacks a DSL.