Recent Posts
Archives
Topics

Tuesday, July 21, 2009

 

VSTO with Outlook 2003

I'm creating a VSTO plugin for Outlook 2003. My problem? I'm using DevStudio 2008 professional.

It has a project template for Outlook 2003 add-ins, but be warned. It is far from complete.

I was able to build my add-in, but distributing it became a nightmare. A royal, amazingly difficult, pain in the gluteus-maximus.

So, how'd I get it installed? First, I had to make sure that the Visual Studio Tools for Office Runtime was installed on each PC.
  • You can download that here

    Next, I had to make sure that (and THIS is the undocumented part that caused me SERIOUS headache) the Primary Interop Assemblies (PIA) for Office 2003 were installed on each PC.
  • You can find those here.

    Next, I had to copy the DLL(s) to the user's hard drive and register each one as able to run. Apparently VSTO (understandably so) has a very very tight security policy. You ahve to register EACH dll that you want to use, and any DLLs that THOSE use. Basically, if you copy it, you register it.

    In order to register the DLL's I used a batch file

    echo Setting Security...
    REM Turn off prompting for a minute.
    "C:\Windows\Microsoft.NET\Framework\v2.0.50727\caspol.exe" -polchgprompt off

    REM Register each DLL using the FULL PATH to the DLL.
    REM Repeat this line for each DLL that you want to register.
    REM I used the same [Project Name] and [Description] for each DLL.
    "C:\Windows\Microsoft.NET\Framework\v2.0.50727\caspol.exe" -u -ag All_Code -url "[Full Path to DLL]" FullTrust -n "[Project Name]" -d "[Description]"

    REM Turn prompting back on. We're done.
    "C:\Windows\Microsoft.NET\Framework\v2.0.50727\caspol.exe" -polchgprompt on


    After setting up all of the applications and security, we now have only one thing left to do... tell Outlook where to find it's new add-in. You ahve to do this via Registry settings.

    I put everything into HKEY_LOCAL_MACHINE instead of HKEY_CURRENT_USER. It seemed to work, but some people suggest against this on the forums. I prefer it, so use which ever one suits you best.

    You can find the registry keys to set here at the bottom of that page.

    After *ALL* of this, I was finally able to get my add-in off the ground. I hope this sincerely helps someone else.

    Labels: , ,


  • Wednesday, January 28, 2009

     

    A lesson in Extension Methods

    With C# 3.0, you were given the ability to extend any object. That's right... if some object objFred has a method that takes two parameters -- string and int -- and you need it to take string and double, you can now "roll your own".

    I needed to do this very thing this week. In my class, I needed to make a call to a system class. But I needed it to return one of the objects I'd created instead of a double or string.

    Yes, I could have just created a method in my own class to handle this, but I wanted to logically keep the method as part of the main library.


    public EngVar ConvertToEng(this string str)
    {
    if (String.IsNullOrEmpty(str)) return null;

    EngVar V = new EngVar();
    try {
    V.Parse(str);
    }
    catch
    {
    return null;
    }
    }


    Now, within my code (as long as this is in scope), intellisense has a new method called ConvertToEng() for every string in my project. Could I have just used the Parse(string x) method within my code? Yes. But having it handy within intellisense makes coding much faster. And I've found that, for my mind, having it hang off of 'string' makes much more sense.


    ...

    string MyString = "E15ft/s^2";

    ...

    EngVar V = MyString.ConvertToEng();



    This just seems to me to be much cleaner and more logical to read.

    Labels:


    Thursday, January 22, 2009

     

    ListView SelectedIndexChanged vs ItemSelectionChanged

    I stumbled upon the most frustrating C# dilemma to-date.

    I was trying to work out a solution to an issue, and -- naturally -- when an item was selected in my list of items, I wanted to do some magic. What would you reach for in this case? The standard SelectedIndexChanged event. That's what we use in Lists, ListBoxes, or even ComboBoxes. That should work in ListViews too... right?

    Wrong. ... sometimes.


    private void SomeList_SelectedIndexChanged(object sender, EventArgs e)
    {
    // Tell me which item in your list just changed... you can't.
    // You only know that SOMETHING changed. Worse than that... for
    // each item that changed, this event fires, but you don't know
    // which one it was nor if the selection went from selected
    // to unselected or the other way around.
    }


    The event exists, so you can use it, but the "sender" is the actual ListView object, and the EventArgs does not contain any handle to let you know which item was selected/unselected. When a user un-selects an item from the list (with six or seven more still selected), I needed to know which one they disabled so that I could do some more magic. But without keeping a list of previously-selected items around, I couldn't immediately tell which one was freshly un-selected.

    ENTER: ItemSelectionChanged.

    This is the correct event to be using in these cases. It exists (apparently) only for ListViews. Why the change? I'm not sure. Backward-compatibility, maybe? Who knows!

    The thing that makes this most useful is that it has an event manager that is much more defined. This event manager returns not only which item is changing, but it tells you whether it is changing from selected to unselected (or vice-versa) and which index it is in the overall list. It's a very beautiful thing.

    So the next time you use a ListView control, keep this little gem in your back pocket.


    private void SomeList_ItemSelectionChanged(object sender,
    ListViewItemSelectionChangedEventArgs e)
    {
    // Note the ListViewItemSelectionChangedEventArgs item.
    // It is a HUGE name, but very important.
    }

    Labels:


    Monday, January 19, 2009

     

    Tag-Data Pairs

    Recently, someone asked me if Tag-Data pairs was a good idea. That's kind of like asking if elephants are good ideas: Sure, if you need a tree pushed over. Not so much if you need something that can fit through your front door.

    As with any design question, what parameters are limiting the idea?

    In general, yes... Tag-Data pairs can be a quick human-readable format that handle non-object-data efficiently. But if you need data that is hierarchical or object-oriented (as most data is), then no. If you need something more than a handful of fields, or if you need these fields to be grouped or bundled in a hierarchy, then use XML. There are a ton of tools built into the .NET platform for handling XML data.

    However, in my previous job, XML was much too bloated for their Nazi-style bandwidth usage. They had to pay per byte of transfer, so each character transferred was precious. In those cases, we actually did use tag-data pairs.

    It was quick, lightweight, simple and human-readable. The most annoying part of a TDP packet was that the data inside was not incredibly easy to get at. For example, if you needed to know the person's last name, you had to parse the TDPs into pairs, then loop through to find the tag that matched "LastName".

    In order to fix this, we needed a lightweight solution that would convert the TDP strings into something more usable... and back again once the values were modified.

    Enter the TDP class. In the end, the implementation of it was almost brainless, but the elegance was in its simplicity.

    public class TDP : Hashtable
    {
    public string Sep1 { get; set; }
    public string Sep2 { get; set; }

    public TDP()
    {
    Sep1 = "=";
    Sep2 = "|";
    }

    public TDP (string initialString)
    {
    Sep1 = "=";
    Sep2 = "|";
    Parse (initialString);
    }

    public string ParseString
    {
    get
    {
    return ConvertToString();
    }
    set
    {
    Parse(value);
    }
    }

    private void Parse(string value)
    {
    this.Clear();
    string[] Pairs = value.Split(Sep2.ToCharArray());

    foreach (string pair in Pairs)
    {
    string[] arr = pair.Split(Sep1.ToCharArray());
    this.Add(arr[0], arr[1]);
    }
    }

    private string ConvertToString()
    {
    StringBuilder sb = new StringBuilder();

    foreach (string key in this.Keys)
    {
    sb.Append(key + Sep1 + this[key].ToString() + Sep2);
    }

    return sb.ToString();
    }

    public override string ToString()
    {
    return ParseString;
    }
    }


    Managing the data became nearly trivial:


    class Program
    {
    static void Main(string[] args)
    {
    TDP tdp = new TDP();

    tdp.ParseString = "Cheryl=Beautiful wife|Jerry=Cheryl's Husband|Time=3:00pm|";

    Console.WriteLine(tdp["Jerry"]);

    tdp["Jerry"] = "Awesome programmer";
    tdp["New tag"] = "Some new tag";

    Console.WriteLine(tdp.ToString());
    }
    }

    Labels:


    Saturday, January 17, 2009

     

    Securing the code with 'License'

    Recently, I've decided to lock down some of the code at work. We've had a problem with some of our code walking out the door without our permission.

    I'm not pointing fingers, but hey... we've gotta stop it from happening. Right? In the end, we need to make sure that our software checks a license file to make sure it has the rights to run.

    Enter System.ComponentModel.License. This is a .NET structure for building your own licensing model. Within this setup, you can "validate" your license however you want to. My license "calls home" to make sure that it has permission to run. Or, you can check a license file on the PC. Or make the user enter a password. You can even have the thing look for a registry entry. Hey. It's up to you.

    Let me show you a quick example of a license library that I've created that will require a flat file on the PC with the words "All Good". If this file exists, the license is good to go.

    I know this particular example is not secure, but I wanted to keep it simple for the sake of example. In order to pull this off, we need to first create two classes that we'll use later.

    MyLicense



    public class MyLicense : License
    {
    private Type type;

    public MyLicense(Type type)
    {
    // use this to get the GUID... which we will use as the LicenseKey.
    this.type = type;

    string txt = File.ReadAllText("c:\\myLicense.lic");
    if (txt != "All Good")
    {
    throw new LicenseException("Unable to validate license");
    }
    }

    public override string LicenseKey
    {
    get
    {
    // Simply return the application's GUID
    return type.GUID.ToString();
    }
    }

    // Required.
    public override void Dispose() { }
    }


    MyLicenseProvider



    public class MyLicenseProvider : LicenseProvider
    {
    public override License GetLicense(LicenseContext context, Type type,
    object instance, bool allowExceptions)
    {
    // I want to catch any file errors and throw a license error instead.
    try
    {
    // Use this flag to license the runtime of your code from the
    // development of your code.
    if (context.UsageMode == LicenseUsageMode.Runtime)
    {
    MyLicense L = new MyLicense(type);
    return (License)L;
    }
    }
    catch (Exception ex)
    {
    throw new LicenseException(type, instance, ex.Message);
    }

    return null;
    }
    }


    With these two classes, we can now lock down our code with our simple license file. Note: The License model from Microsoft requires that it be applied to a class, not a program or static object.

    Our Program



    [LicenseProvider(typeof(MyLicenseProvider))]
    public class MyClass
    {
    License Lic;

    void MyClass()
    {
    // The very first thing we're going to do is see if we
    // have permission to be here.
    Lic = LicenseManager.Validate(typeof(MyClass), this);
    }
    }


    Now, we've got the base code. But what does it do? When you create an instance of "MyClass" to be used in your code, it -- of course -- runs the constructor. The first line of the constructor is :

    Lic = LicenseManager.Validate(typeof(MyClass), this);


    Thanks to the pre-processor line of LicenseProvider, it knows which class to call: MyLicenseProvider. Since MyLicenseProvider is a type of LicenseProvider, the License engine in .NET knows that GetLicense exists, so it gets called, which calls an instance of "MyLicense".

    NOW we get to the good stuff. Inside this license library, you can do ANYTHING. However you want to validate your license is up to you. I'm going to simply open a file, if it even exists, and see if it has the right string in it. If so, we're golden.

    However, if the license process should fail or return null for any reason, the LicenseManager from the constructor in our class will throw a LicenseException and the whole thing will come to a screeching halt.

    Nice, isn't it?

    And now, you know the basics for locking down your code. How you choose to handle these basics for doing more in securing your code is up to you.

    P.S. And for you developers... keep in mind that you can require a development license SEPARATE from your run-time license, giving you the ability to make money from your work without the fear of it growing legs and breeding like roaches in the cesspool of internet piracy.

    Labels:


    Wednesday, January 14, 2009

     

    Command Line Parsing

    I recently had a case where I needed to handle a series of command line arguments. I needed to know if flagA had been set with flagB, but not with flagC and flagD.

    Ripping through the list of command line arguments is easy enough, but tedious, so I wrote my own CommandLine class that lets me reference flags by name. For instance, if I need to check to see if flagA exists, I can just say cmd.ContainsKey("flagA").

    The best part is that it only modified my master source by one line:

    CMD cmd = new CMD(args);


    I thought that this little ditty -- while being trivial in concept -- was worth sharing. If you find it useful, let me know. Also, if you have comments, feel free to post them.


    public class CMD : Hashtable
    {
    public CMD(string[] args)
    {
    if (args == null) return;

    for (int x = 0; x < args.Length; x++)
    {
    if (args[x].StartsWith("-") || args[x].StartsWith("/"))
    {
    // take off the "-"
    string key = ((string)args[x]).Substring(1);

    // We have a flag. Is it a TF or a value flag?
    if ((x < args.Length - 1) && (!args[x + 1].StartsWith("-")))
    {
    // VALUE flag
    this[key] = args[x + 1];
    x++;
    }
    else
    {
    // True/False flag
    this[key] = true;
    }
    }
    }
    }
    }


    Once you've defined the class, accessing the command line arguments becomes trivial. In the example below, I have a simple console application that needs command line parsing. Once CMD is initialized, I can access CMD from anywhere, and access all of my command line parameters by name.


    class Program
    {
    static CMD cmd;

    static void Main(string[] args)
    {
    cmd = new CMD(args);

    // Do some magic stuff here
    }
    }

    Labels:


    Saturday, January 10, 2009

     

    More File Permissions

    In doing more research with file permissions, I've found that MS shares file permissions and directory permissions. (if you dig into the enums enough, you'll see duplicate values for multiple enum settings)

    I thought this was odd until I realized why. The FileSystemRights enum contains values for both FILES and DIRECTORIES. So, ListDirectory for directory access is the same flag as ReadData for file access.

    This may seem trivial until you want to list the contents of a directory, but not actually allow a person to view the file... which is exactly what my boss asked me to do.

    "Easy!" I said as I jumped to my desk. Until I discovered that -- for reasons unknown to me -- we must use file inheritance.

    So when the file inherits "ListDirectory" from it's containing folder... it is, in fact, inheriting the "ReadData" flag for the file.

    In short: If you use file inheritance, and you specify ListDirectory, you cannot make the file ReadOnly without going to the file and explicitly denying ReadData for a user. (UGH!)

    Labels:


    Friday, January 9, 2009

     

    Rounding errors...

    This week, my life has been plagued with rounding errors. I have a library that converts to/from different units of measure. This library is used within several objects with the idea to store distances in memory all in meters. Then, if the user wants to see that measurement in feet, or inches, or miles, or kilometers... you get the idea.

    Here's my problem... Some calculations need to be done in feet. Others in inches. With this nice, neat, creative library, this should be EASY... right?

    I couldn't have been more wrong.

    The library uses the following units for conversion from meters to feet and inches. Keep in mind that I'm keeping all units in meters in memory.

    double ft = meter / 0.3048;
    double inch = meter / .0254;


    To keep it in meters in memory, when the user inputs a number, I have to convert that number from its respective distance into meters. I use the following conversions for that.

    // if given inches
    double meter = inch * 0.0254;

    // if given feet
    double meter = ft * 0.3048;


    Since it's divide for one, and multiply to put it back, it should come out the same... right? WRONG!

    Take a look at an input of 220 feet:

    // Store the value in memory...
    double meter = Convert.ToMeter(220); // 67.056

    // convert that back into "feet" for display.
    double ft = Convert.ToFeet(meter); // 219.99999999999997


    That's right. 67.056 / 0.3048 rounds to 219.9999999... NOT 220. Keep in mind that 67.056 isn't a rounded number itself. That's the actual, complete number for 220 * 0.3048.

    In my calculations, I then tried to say "if (Feet entered by user) == (Feet previously entered by user) then go do some stuff" and it never would.

    Here's the magic bullet for this issue: First, determine how many significant digits you care about. For most of my calculations, 8 digits is absolute overkill. So, for cases like this, I will use 8 as my cutoff.

    My conversion class now has a property called "SignificantDigits" that defaults to 8. Inside my conversion routines, before I return the value, I round the value to the significant digits. So far, that's been working very well for me.


    // Forcibly round to 8 significant digits to prevent arbitrary rounding errors.
    double ft = Math.Round(meter / 0.3048, SignificantDigits);
    double inch = Math.Round(meter / .0254, SignificantDigits);


    For those needing a very accurate number, you'll have to set the significant digits count so high that this approach will not be feasible. But for the average Joe making the average calculations, this is a quick fix.

    Labels:


    Thursday, January 8, 2009

     

    C# File/Directory Permissions

    Today's code problem comes from my real-world experience. I was working on an app to help manage file-rights here at my job.

    The frustrating part was that it looked so simple. The default code from Microsoft shows that this is almost trivial:

    // Get the directory security object.
    DirectorySecurity dSecurity = Directory.GetAccessControl(dirName);

    // change the rights on the object.
    dSecurity.AddAccessRule(new FileSystemAccessRule(account,
    rights, controlType));

    // Set the new access settings.
    Directory.SetAccessControl(dirName, dSecurity);


    But when you do it this way, you can no longer see the checkmarks in the default security tab. You have to go into "Advanced" to see your permissions.

    Why?

    Because doing it this way sets all of your permissions as "special" permissions, not any of the default standard permissions. Note that your file permissions are set properly. The rights really are set. You just can't see the nice little check marks in windows.

    I did some research to set the ACE (Access Control Entry) so that the checkmarks would re-appear, and found the following snippet of code:


    dSecurity.AddAccessRule(new FileSystemAccessRule(
    IdentityReference, FileSystemRights,
    InheritanceFlags.ContainerInherit |
    InheritanceFlags.ObjectInherit,
    PropagationFlags.None,
    AccessControlType));


    So if I just do this after I set the new security the magic little checkboxes will reappear, right? YES! They did. But then all of the checkboxes for all OTHER users of that file or directory went away!!!

    (*sigh*)

    So, after doing some research, testing and playing, I've found a hack that will re-set the nice little checkboxes in the security tab. It's ugly. It's painful. But it works.... mostly. (more on that in just a second.)



    DirectorySecurity dSecurity = Directory.GetAccessControl(dirName);

    // change the rights on the object.
    dSecurity.AddAccessRule(new FileSystemAccessRule(account,
    rights, controlType));

    // Call my Bug-Fix routine
    MSBugFix(dSecurity);

    // Set the new access settings.
    Directory.SetAccessControl(dirName, dSecurity);


    ...


    private void MSBugFix(DirectorySecurity dSecurity)
    {
    // RESET EVERYONE'S ACE So that it will view properly
    // in the Security tab. (MS BUG FIX!!)
    AuthorizationRuleCollection acl = dSecurity.GetAccessRules(
    true, true, typeof(System.Security.Principal.NTAccount));

    foreach (FileSystemAccessRule ace in acl)
    {
    dSecurity.AddAccessRule(new FileSystemAccessRule(
    ace.IdentityReference, ace.FileSystemRights,
    InheritanceFlags.ContainerInherit |
    InheritanceFlags.ObjectInherit,
    PropagationFlags.None, ace.AccessControlType));
    }
    }


    This works, but can be a bit slower if you have many files to do. Gee... I wonder why? Oh.. right. The nasty ALL USERS loop in there. But it does work.... until you install Service Patch 3 on the machine.

    When you install Service Pack 3, the nice little checkboxes go away again. Note that your permissions and access to the files remain. You can still get to your files, you just have to go to "Advanced" to see the actual settings on your files.

    If anyone has an actual fix for this issue, (not the ugly hack I had to develop) I would love to hear it. Until then, we just won't be installing SP3.

    Labels:


    This page is powered by Blogger. Isn't yours?

    Subscribe to Posts [Atom]