29 September, 2006

Crystal Reports in VS 2003 IDE

Fixing Data Issues in Crystal Reports

I was tasked with making alterations to a set of Crystal Reoprts created by an off-shore company. I had never used Crystal Reports, either in VS.NET or anywhere else.

The first task was to change the length of a data field. I made the appropriate alterations to the data table, changing the allowable number of characters from 15 to 50. This worked just as expected in the C# application. But it was not working in the Crystal Reports report. The generated report still only showed a max of 15 characters.

So I fired up the IDE and opened up the proper .RPT file. Looking at this thing was like something there are no words to describe. I loathed working in it. I haven't yet been able to find a way into the guts of the report, to edit things manually, sans the GUI. I don't believe there is a way to do this. It's all done through the GUI, which, in my book, is a huge minus. The whole reason I never ever bought a Mac was because there was no command line interface. Okay, I know that now there is a command line in the Mac OS, but too little too late.

Back to the problem at hand. I found the field in question in the report, right clicked it and was presenting with a menu I didn't fully understand. But hey, it says Format..., that's go to be where to change the field length. WRONG. The only thing format does is format the text, you know, font, color, weight, size, position, all that jazz.

I hit "Browse Field Data..." not really knowing what would happen. I sent me into a wizard to determine which database to use, I selected the proper database, but it showed only 15 of the 50 characters I had in the database for that field. Strike Two.

Select Expert... did nothing for me. It sent me into another wizard interface that didn't have anything to do with field size. And, still, after two days of messing with this thing, I don't know what an expert is.

To the left of the main window, Crystal Reports has a "Field Explorer" which is kind of like the Server Explorer VS has. Right clicking on the database brings up a different context menu. Oooh, what's this? I see there is an item named "Verify Database". This should fix it. I hit that sucker and the thing starts moving, things are happening, I know this because I'm getting tons of dialog boxes talking about what is being updated.

After it finally finishes, I right click on the report field again, and do the "Browse Field Data..." to see if anything has changed. VOILA! It displays the whole 50 characters. Excellent. That was easy! Rebuild the application and run the report. Doesn't work. I am now getting the error:"Error in formula. 'TableName.FieldName' This field name is not known."While I know good and well that field is in that table which is in the database.

Next thing to try was to re-create the report. I copy the report to another file, and remove the data connection and re-connect. But this method ended up giving me 26 prompts for data. After entering the proper data in all 26 prompts, the reports generated properly. However, the user should not have to enter the same piece of data 26 times in order to see the report. Espcially since the probability of the user knowing the correct data to enter is very, very slim.
Finally, I clicked enough menu items to obtain a solution. I selected the Database Expert, the Set Location option and created a new connection, selected the proper database. Finally after 2 1/2 days of messing around with all of this, it worked, and I was able to put this horrid experience behind me.

27 September, 2006

Getting Disk Information in C#

Using System.Management and Win32_LogicalDisk


In my last post, I briefly spoke about finding drive information in a C# application. The problem I've been trying to solve is that I have a windows form that needs to run at the end of an install. The form allows the user to copy files from a specific location on the install DVD to the users machine. The files are about 1.3Gb, and I didn't really want to package all that data into one MSI file. A problem arose when the install was finished, and the little windows application was copied to the user's machine, how can I find the DVD drive? And after I found a DVD drive, how can I make certain it is the correct drive with the correct disk?

I started out by importing the kernel32.dll and using the external GetDriveType(string drive). This worked, but I was really wanting to find a C# way to do this without having to import anything else. Enter System.Management - which allows a user to get all of this information natively in C# using Win32_LogicalDisk. Win32_LogicalDisk give you access to a plethora of information on all physical and mapped network drives. You can get the drive name, device id, volume name, serial number and on and on. In addition, you can run SQL type queries against the drive to get just the desired information from the desired drives or drive types or drive sizes and so forth.

Since I am interested in finding CD/DVD drives, I had to find how to tell what kind of drive I was looking at. The following (from the above linked MSDN page) table was a great help:
























Value
Meaning
0
Unknown
1
No Root Directory
2
Removeable Disk
3
Local Disk
4
Network Drive
5
Compact Disc
6
RAM Disk

Using System.Management.ManagementObjectCollection and System.Management.ManagementObjectSearcher I was able to create a collection of all drives with a device type of 5.

ManagementScope ms = new ManagementScope();
ObjectQuery oq = new ObjectQuery("SELECT DeviceID, VolumeName FROM Win32_LogicalDisk WHERE DriveType= 5");
ManagementObjectSearcher mos = new ManagementObjectSearcher(ms,oq);
ManagementObjectCollection moc = mos.Get();


Then I loop through the collection to check out each object's information:

foreach (ManagementObject mo in moc)
{
if (string.Compare(mo["VolumeName"].ToString(),"myString",true) == 0)
DirectoryInfo newDI = new DirectoryInfo(System.IO.Path.Combine(mo["DeviceID"].ToString(), "myPath"));
}

(thanks Shawn, for pointing out the error where I tried to get the volume name from the ManagementObjectCollection, instead of the ManagementObject. While here, I thought I'd go ahead and do the string comparison correctly too. :) )

So now I have what should be the proper path to the files I want to install.

Another route to this information is via the ManagementClass class. It is a little cleaner, and a bit quicker to code, but does give you more than you might need for your particular purpose.

ManagementClass mc = new ManagementClass("Win32_LogicalDisk");
ManagementObjectCollection moc = mc.getInstances();
foreach (ManagementObject mo in moc)
{
// Your code here
}


Few less lines of code, but a much more generic return on the drive information.

Windows Installer Woes

Launching a windows application after install completes in Windows Installer


I was tasked at work to become the "install czar" so to speak. For this, I needed to learn some things about the Windows Installer. While there is a pretty good guide for the Windows Installer at Microsoft's MSDN site, much of the information was elusive. For instance, I needed information about custom actions. For one installation I created a windows forms application which would copy a boat-load of images from the install DVD to the client machine. The images were about 3Gb in total size, so I didn't want to package them in the install. The install contained a small sampling of the images, and it was bulging at 300+Mb - this takes such a long time to process, checkout, build etc.

MS has a pretty good CustomAction Reference page, but it didn't cover everything I was looking at. I went into the setup project I had created in VS 2003, added the .EXE I had created and added it to the "Install" custom action. It seems to me, this should have worked, but it didn't. It appeared the EXE was never called. So I downloaded the Windows Platform SDK which comes with an ultra-cool tool called Orca which allows editing of the MSI tables. Looking at the tables, the EXE being called had a type of 1024. However, there was not 1024 type in the reference page.

Long story short, I found this blog from some guy calling himself Boneman, which is an excellent tutorial for Custom Actions in the Windows Installer.

I tried everything I could think of to get this stupid application to launch at the end of the install - nothing seemed to work. Then I went and revisited the Custom Actions section in the VS IDE. What I found was a property settings named "InstallerClass". I set that to "False" and voila! It worked!

But my application was flawed. I didn't know exactly what the working directory would be, I thought it would be the directory from which the install was launched. I was wrong, it was the directory in which the application file was placed. Now I needed to find the correct DVD drive in order to get the images off of it. To accomplish this goal, I imported the kernel32.dll to access the GetDriveType and GetVolumeInformation methods. These two methods return to me the type of drive, and all of the drive information you think you might ever want. Well, actually, the GetDriveType returns a System.Long value, and you have to know what each value represents. Here is how I figured drive type:


[DllImport("kernel32.dll")]
public static extern long GetDriveType(string driveLetter);

private int
DriveType(string asDrive)
{
if ((GetDriveType(asDrive) & 5) == 5)
return 5;
if ((GetDriveType(asDrive) & 3) == 3)
return 3;
if ((GetDriveType(asDrive) & 2) == 2)
return 2;
if ((GetDriveType(asDrive) & 4) == 4)
return 4;
if ((GetDriveType(asDrive) & 6) == 6)
return 6;

return 0;
}


Now, figuring out that 5 is the CD drive took quite a bit of searching, but having the Platform SDK mentioned above sure helps. At any rate, I got the CD drive, then looked for the volume name that I named the DVD during creation. If it locates that and the proper directory is on that disc, then it copies the files. If not, I pop up a folder browse window to allow the user to determine where the image files are.