21 December, 2006

EntLib starting to come around

We are focusing on three blocks in the Enterprise Library; Logging, Configuration and Exception Handling. I'm looking at Logging first because everthing else will touch on the logging. What we need is to force three attributes into each log. These three are already provided in by the LogEntry object, these are; Application Name, Application Version and Event ID. Okay, so application version is not supplied by the LogEntry, but we can get that easy enough.

The other thing we want is that the custom logger or tracelistener or whatever it is we create must be inherited and cannot be instantiated on its own. While this sounds easy enough via an abstract class, it turned out not to be so practical. It seems that, while I can create an abstract class that inherits the ILogFormatter, that class can't be used in the Enterprise Library Configuration tool.

Hmmmm...I wonder if we can extend the Enterprise Library Configuration tool to force the required attributes and formatters. Now I have something else to look at.

I've been able to find some information on using Enterprise Library. One place which has links to tutorials (or at least is supposed to) is Channel9 Wiki. However, most the tutorials that would actually do me any good are by that Hisham Baz fellow, and I can't seem to get to his site from work. I'll have to try from home, perhaps it is just that exceptionally poor quality of internet service we experience here:

Channel9 Wiki
EnterpriseLibrary Home
EnterpriseLibrary MSDN blogs
MSDN EnterpriseLibrary page
Hands on Labs - EnterpriseLibrary 2

15 December, 2006

Oh the confustion is causes!

Trying to find information on how to use the Enterprise Library is like looking for information on how to use the Enterprise Library! This is worse than needle in a haystack scenario. I see lots of how EntLib works, and why it works, but very little on practical applications of EntLib. I should probably look at the documentation that came with the EntLib download. I haven't yet - but mostly because Microsoft has a history of piss-poor documentation (when there is any at all).

More soon!

07 December, 2006

Enterprise Library

We're going to start using Enterprise Library for .NET Framework 2.0 to handle our configuration, exception handling, and logging processes. This is going to be fairly new because we've never been ones to actually follow recommended practices on most of anything. Maybe we'll end up with some readable code that actually works. :)

Anyway, I'll be posting in the near future the experience about a first time user with this library and it's implications.

Thanks for visiting!

01 December, 2006

Other places of interest

I have several other blogs, they are updated as my schedule permits, but at least a couple of times a week.  You can get there via the following links:

My Home Town
Bigsibling
At The Top Of My Lungs
C# Follies

Using CACLS in an MSI install

What I have discovered is that if you use the Setup and Deployment project from the Visual Studio 2003 IDE, it doesn't always work the way one might expect it to work.

For instance, when the user installs the program, they are presented with the option to select to install the program for them only, or for anyone on that machine. If the user selects for them only, it would be expected that the files and components of that program's permissions are set so as to only allow that person to run the program.

However, if the user selects to allow all users to run the program, my prevailing expectation was that it would set permissions on the files and components to allow any user to run the program. WRONG. As I should have expected, with Microsoft making so many assumptions and decisions for us (and giving us very little means to adjust that easily), the files and components are set to read only for all but admin account holders.

In our programs, there are many files that are read and written to. For this, since we have dropped InstallShield as our primary install creating agent, I had to create yet another small program that runs on install (that makes two now). I do have these installed as "hidden" files so hopefully it won't clutter up the install directory too badly.

At any rate, I use CACLS.EXE to set the directory permissions. It's a fairly simple command line program. As I didn't want to pop open an command line window, I created a form that is 1px by 1px, located at 0,0 and has an opacity of 0. This should keep it from being seen at all. What we needed was for the local "Users" group to have read/change permissions to all files that get installed. For that I used the command line switch "/G BUILTIN\Users:C(hange)". This table lists the permission codes:




















NNone
RRead
WWrite
CChange
FFull

The codes really are (surprisingly) intuitive. There are other switches involved. These are




























/TRecursive through current and all sub-directories
/EEdits permissions (doesn't replace them)
/CIgnores "Access Denied" errors
/GGrant access rights
/RRevoke access rights
/PReplace access rights
/DDeny access rights


So I end up with the following example:

string response;
string userPerm = @"BUILTIN\Users:C";
string fileDirectory = @"C:\Program Files\Program";
string caclsSwitches = @" /T /E /C /G ";
string caclsLoc = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "CACLS.EXE");
Process p;
if (Directory.Exists(fileDirectory)
{
p = new Process();
p.StartInfo.FileName = calsLoc;
p.StartInfo.Arguments = fileDirectory + caclsSwitches + userPerm;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
p.StartInfo.CreateNoWindow = true;
p.Start();
response = p.StandardOutput.ReadToEnd();
if (response == null || response.Lenghth == 0)
{
// handle errors here
}
if (p != null) p.Dispose();
response = null;
}



Resources:
Undocumented CACLS: Group Permissions Capabilities
How to Use CACLS.EXE in a Batch File
Information about the cacls command.

Technorati

I found this site where blogs are listed and indexed and thought I'd sign up.


Technorati Profile

06 October, 2006

Creating an Add-In for VS.NET 2003

Eh, nevermind (25 October, 2006)

So, as Mathieu so kindly pointed out in the comments section, PushOK does a VS.NET 2003 add-in for CVS rather inexpensively. While I was looking forward to actually creating an add-in for VS.NET, the time I have to play with it just doesn't exist, so I'm scrapping this one. I may play with it some in VS.NET 2005 at home (I don't have 2003 installed at home anymore), so this might actually go somewhere, but who knows where and when.

Starting Out (06 October, 2006)


My company is switching is source control from the woefully in adequate Visual SourceSafe to CVS. I don't know why they refuse to go to Microsoft Team Server, but I suspect that it has a great deal to do with CVS being free, and TeamServer being expen$ive.

Specifically, we are instructed to use the WinCVS client to access the source code in CVS. After plenty of reading, I realized there is currently no way to access WinCVS through the VS.NET IDE like there is for VSS. I have never created an Add-In for VS before, and thought it sounded somewhat interesting task to tackle. I know it is possible to create one because I see them for sale online for anywhere from $10 to over $200.

I did some queries on Windows Live search looking for add-in information. I got a lot of places selling add-ins, but nothing in the way of how to create one. Then I got the bright idea thinking that if any place would have information on creating an add-in for VS, it would be VisualStudio Managzine.

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.