Tuesday, August 25, 2009

Cannot start Microsoft Office Outlook. Cannot open the Outlook window.

Today Outlook doesn't start. It gives me the error message "Cannot start Microsoft Office Outlook. Cannot open the Outlook window.". Probably from the crash my computer had yesterday.

Trying scanpst.exe to repair. But it wasnt the problem this time.

Theese magic lines from command prompt solved the problem:
Outlook.exe /resetnavpane

Then the day can begin.

Monday, August 17, 2009

Automated Integration Testing WCF Services

Automated testing, both unit and integration, is the holy grail these days. I’m all up for the concept, and I’ve done a fair part of testing myself already. I realized at one point that my unit test were really integration test. But started with stubs and mocks, making my unit tests happy and green. But we all have to start somewhere, right?

But there are pitfalls, a lot of them. I’ve had a few projects in row now where we have used WCF Services as ordinary Web Services. Cause we are communication with “others”. Fairly simple, just add basicHttpBinding on your endpoints.

Exception logging
I want exception logging to happened on all my public methods. I prefer doing this with a [ErrorHandlerBehavior] described in another article here on my blog. This works fine, I am kind a liking that some magic happens on my service class, with logging the exception and throws it again.

Application Initialization
But then I realize I need a Application Start, to make some global configuration and settings run. This might be NHibernate etc. Now if I presume my WCF Service would always run inside IIS, I could use Global.asax in my service. Just add this file to the project, and hook up like this:
This is supposed to be a ordinary Web Service, so there is no problems with that. Except for my automated tests. When my test project launches, the service is hosted in the WCF Service Host. Which isn’t IIS. Obviously. Now I have to make my own Service Host, which triggers something like application start.

I fooled around with the AppInitialize method: public static void AppInitialize();
Which supposedly can be put in any type that is defined in a C# file in the application’s \App_Code directory. When the AppDomain is started, ASP.NET checks whether there is a type that has such as method (exact name and signature) and invokes it. I’m not sure if this could have work with an WCF Service, but I couldn’t make this work.

Self Hosting
So I made my WCF Service self hosted when tests are ran, but “normal” IIS hosted when run in production and test environment.

I used the SelfHosting.cs and the test script would look something like this:

[TestMethod]
[ExpectedException(typeof(FaultException<string>))]
[DeploymentItem(@"C:\\Code\\Foo\\Bin\\")]
public void OrderService_MyException_FaultExceptionWithMyArgumentExceptionAsString()
{
using (var host = new SelfHosting())
{
host.Start();
using (var client = new OrderServiceClient())
{
try
{
client.MyException();
}
catch (FaultException<string> ex)
{
Assert.AreEqual("System.ArgumentException", ex.Message);
Assert.AreEqual("MyException", ex.Detail);
throw;
}
}
}
//there should have been done some error logging by now.
}

Friday, August 14, 2009

Lotus Notes ODBC Driver Connecting to UNC Path

I’ve previously made a WebService that encapsulates calls to a Lotus Notes database. From this database I extract meeting rooms and today’s meetings for each room. This information is used by others to show on monitors beside each meeting room. They just call my service, and get their xml back. All the “ugly” communication with Lotus Notes is completely hidden from them.

The service hasn’t been all that stable, not my service fault though, but since the service hides all the Notes problems – it is easy to blame the messenger. I was not initially allowed to read directly from the master database file (database.nsf). That is placed behind firewalls on another server. The workaround for this was setting up a replication. This worked very well, as long as someone is logged on the console, on the server. See the problem… Sometimes the service shows old data that is displayed beside the meeting rooms. Causing confusion about who has reserved the room or not. I’m not a Notes-kind of guy, so this might have been set up more robust or elegant.

But now I am given a hole in the firewall, to a network file share, directly to the master database file. Which in Lotus Notes is an nsf file. The service can now access the file, no longer to the replica C:\Program Files\IBM\Lotus\Domino\Data\FooCopy.nsf, but directly to the \\fooserver\notes\Foo.nsf. The file share is protected with access only to a given user. Accessing UNC Path is supported with the Lotus Notes ODBC Driver. But I ran into some problems with the UNC Path logon.

Previously I’ve used impersonation when accessing the UNC Path. So I tried this first. Impersonation & IDisposable worked seemingly very well. But the ODBC driver was still not happy. I fooled around quite a bit with the Win32API Impersonation, changing the dwLogonType and more. But it would not work. The service could now see the file, and read from the file. But the ODBC driver gave me errors:

ERROR [S1000] [Lotus][ODBC Lotus Notes]Access to data denied.
ERROR [IM006] [Microsoft][ODBC Driver Manager] Driver's SQLSetConnectAttr failed
ERROR [01000] [Microsoft][ODBC Driver Manager] The driver doesn't support the version of ODBC behavior that the application requested (see SQLSetEnvAttr).

Then I thought of mapping the share with letter would be the simplest and easiest solution. Why not do this programmatically. I used a Win32 API called WNetUseConnection to connect to a UNC path with authentication. This will allow you to connect to a remote machine, even if it is not on the same domain, and a given username and password. I found this thread, with this code sample. It worked smoothly. The service could both see the file, and the ODBC driver didn’t object anymore. Problem solved!

Code sample is quoted here:

public class PinvokeWindowsNetworking
{
#region Consts
const int RESOURCE_CONNECTED = 0x00000001;
const int RESOURCE_GLOBALNET = 0x00000002;
const int RESOURCE_REMEMBERED = 0x00000003;

const int RESOURCETYPE_ANY = 0x00000000;
const int RESOURCETYPE_DISK = 0x00000001;
const int RESOURCETYPE_PRINT = 0x00000002;

const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001;
const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002;
const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003;
const int RESOURCEDISPLAYTYPE_FILE = 0x00000004;
const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005;

const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
const int RESOURCEUSAGE_CONTAINER = 0x00000002;


const int CONNECT_INTERACTIVE = 0x00000008;
const int CONNECT_PROMPT = 0x00000010;
const int CONNECT_REDIRECT = 0x00000080;
const int CONNECT_UPDATE_PROFILE = 0x00000001;
const int CONNECT_COMMANDLINE = 0x00000800;
const int CONNECT_CMD_SAVECRED = 0x00001000;

const int CONNECT_LOCALDRIVE = 0x00000100;
#endregion

#region Errors
const int NO_ERROR = 0;

const int ERROR_ACCESS_DENIED = 5;
const int ERROR_ALREADY_ASSIGNED = 85;
const int ERROR_BAD_DEVICE = 1200;
const int ERROR_BAD_NET_NAME = 67;
const int ERROR_BAD_PROVIDER = 1204;
const int ERROR_CANCELLED = 1223;
const int ERROR_EXTENDED_ERROR = 1208;
const int ERROR_INVALID_ADDRESS = 487;
const int ERROR_INVALID_PARAMETER = 87;
const int ERROR_INVALID_PASSWORD = 1216;
const int ERROR_MORE_DATA = 234;
const int ERROR_NO_MORE_ITEMS = 259;
const int ERROR_NO_NET_OR_BAD_PATH = 1203;
const int ERROR_NO_NETWORK = 1222;

const int ERROR_BAD_PROFILE = 1206;
const int ERROR_CANNOT_OPEN_PROFILE = 1205;
const int ERROR_DEVICE_IN_USE = 2404;
const int ERROR_NOT_CONNECTED = 2250;
const int ERROR_OPEN_FILES = 2401;

private struct ErrorClass
{
public int num;
public string message;
public ErrorClass(int num, string message)
{
this.num = num;
this.message = message;
}
}


// Created with excel formula:
// ="new ErrorClass("&A1&", """&PROPER(SUBSTITUTE(MID(A1,7,LEN(A1)-6), "_", " "))&"""), "
private static ErrorClass[] ERROR_LIST = new ErrorClass[] {
new ErrorClass(ERROR_ACCESS_DENIED, "Error: Access Denied"),
new ErrorClass(ERROR_ALREADY_ASSIGNED, "Error: Already Assigned"),
new ErrorClass(ERROR_BAD_DEVICE, "Error: Bad Device"),
new ErrorClass(ERROR_BAD_NET_NAME, "Error: Bad Net Name"),
new ErrorClass(ERROR_BAD_PROVIDER, "Error: Bad Provider"),
new ErrorClass(ERROR_CANCELLED, "Error: Cancelled"),
new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"),
new ErrorClass(ERROR_INVALID_ADDRESS, "Error: Invalid Address"),
new ErrorClass(ERROR_INVALID_PARAMETER, "Error: Invalid Parameter"),
new ErrorClass(ERROR_INVALID_PASSWORD, "Error: Invalid Password"),
new ErrorClass(ERROR_MORE_DATA, "Error: More Data"),
new ErrorClass(ERROR_NO_MORE_ITEMS, "Error: No More Items"),
new ErrorClass(ERROR_NO_NET_OR_BAD_PATH, "Error: No Net Or Bad Path"),
new ErrorClass(ERROR_NO_NETWORK, "Error: No Network"),
new ErrorClass(ERROR_BAD_PROFILE, "Error: Bad Profile"),
new ErrorClass(ERROR_CANNOT_OPEN_PROFILE, "Error: Cannot Open Profile"),
new ErrorClass(ERROR_DEVICE_IN_USE, "Error: Device In Use"),
new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"),
new ErrorClass(ERROR_NOT_CONNECTED, "Error: Not Connected"),
new ErrorClass(ERROR_OPEN_FILES, "Error: Open Files"),
};

private static string GetErrorForNumber(int errNum)
{
foreach (ErrorClass er in ERROR_LIST)
{
if (er.num == errNum) return er.message;
}
return "Error: Unknown, " + errNum;
}
#endregion

[DllImport("Mpr.dll")]
private static extern int WNetUseConnection(
IntPtr hwndOwner,
NETRESOURCE lpNetResource,
string lpPassword,
string lpUserID,
int dwFlags,
string lpAccessName,
string lpBufferSize,
string lpResult
);

[DllImport("Mpr.dll")]
private static extern int WNetCancelConnection2(
string lpName,
int dwFlags,
bool fForce
);

[StructLayout(LayoutKind.Sequential)]
private class NETRESOURCE
{
public int dwScope = 0;
public int dwType = 0;
public int dwDisplayType = 0;
public int dwUsage = 0;
public string lpLocalName = "";
public string lpRemoteName = "";
public string lpComment = "";
public string lpProvider = "";
}


public static string ConnectToRemote(string remoteUNC, string username, string password)
{
return ConnectToRemote(remoteUNC, username, password, false);
}

public static string ConnectToRemote(string remoteUNC, string userName, string password, bool promptUser)
{
NETRESOURCE nr = new NETRESOURCE();
nr.dwType = RESOURCETYPE_DISK;
nr.lpRemoteName = remoteUNC;

int ret;
if (promptUser)
ret = WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null);
else
ret = WNetUseConnection(IntPtr.Zero, nr, password, userName, 0, null, null, null);

if (ret == NO_ERROR) return null;
return GetErrorForNumber(ret);
}

public static string DisconnectRemote(string remoteUNC)
{
int ret = WNetCancelConnection2(remoteUNC, CONNECT_UPDATE_PROFILE, false);
if (ret == NO_ERROR) return null;
return GetErrorForNumber(ret);
}
}

Saturday, August 01, 2009

Hotfix KB 971092 for Visual Studio 2008 SP1 install problems

Hotfix KB 971092 for Visual Studio 2008 SP1 install problems. I found this workaround and it did the trick for me.

Applies to:
Security Update for Microsoft Visual Studio 2008 Service Pack 1 (KB971092)

Symptoms:
The update installs successfully and but will be offered again by Windows Update as a new update. This can be repeated indefinitely.
or
the standalone installer tells you that this update "does not apply, or is blocked by another condition on your system".

Workaround (found here):
1. Download the stand-alone version of VS90SP1-KB971092-x86.exe.
2. Start the installation VS90SP1-KB971092-x86.exe
3. Wait for the error message to come up – do not close the window
4. Copy the temp. (ex C:\4e9d64946a9fc8a7e9c1c82f02d964) folder where the patch has been unpacked to a new folder, for example onto your desktop.
5. Close VS90SP1-KB971092-x86.exe that you started in step 2.
6. Navigate to {Program Files}\Microsoft Visual Studio 9.0\Common7\Tools and find vsvars32.bat.
7. Change the permissions on the file to allow everyone to edit it.
8. Start VS90SP1-KB971092-x86.msp from within the saved folder and the process should complete.
9. You may want to reset permissions that you gave in step 7.