Tag Archive for .NET

Debug and Conquer!

A few tips to keep in mind when developing and debugging a Smart Document using C# (and probably VB.NET too).
(For my own selfish ease-of-use, I will refer to Word exclusively here. All these steps should apply to Excel equally).



  1. When building the Smart Doc’s Manifest.xml, make sure to include the tag for both the solution DLL and the schema.. This makes sure Word doesn’t copy the DLL and such to the current user’s profile, and thus uses the same DLL that Visual Studio is debugging.
  2. Create an empty document and attach it to the schema and the expansion pack. Then save it somewhere.
  3. In the Project Properties -> Configuration Properties -> Debugging -> Start Action, set Winword.exe as your default application to run when debugging. Pass the name and path of the document from section #2 in the Command Line Arguments field. That way we always launch Word when debugging, and don’t have to mess with attaching to process and so on.
  4. In the Post Build Event for the project, add the following line:
    C:WindowsMicrosoft.NETFrameworkv1.1.4322gacutil.exe /cdl
    (You might need to replace C:Windows with C:WINNT for Windows 2000. Also replace with the version of the framework you’re using).
    This command clears the Download Cache. If we don’t run it, Word will use an older, cached copy of the DLL.
    Note: If Word is running, this command will fail. Don’t forget to close Word.
  5. Once we’re in Word and debugging and all, it’s best to manually close the document rather than just brutally stopping the debugging session. Otherwise we’ll get all those “Word recovered your document from the sudden crash, do you want to restore it?“ questions and taskpanes and all popping up. Best to spare yourself the aggravation and close it politely yourself.

Those should get us started. Now to fight with ActiveX controls…


 

“Smarter than the average Document.”

Smart Document Deployment is definitely an issue that needs a lot of work.


For the past week I’ve been repeatedly stumped in the simple task of loading a Smart Document solution into Word. Not a specific, possibly-buggy SmartDoc – ANY SmartDoc solution. Even ones that worked before. Even the SDK-supplied SimpleSample. ALL.


I followed the MSDN instructions quite specifically. (Here).
I tried to debug using all the information here.
I added Code Access Security.
I changed namespace names.
I tried read the Fusion log to see what’s going on.
I uninstalled and reinstalled Office 2003 TWICE, in two different languages.


And all this time this nagging, teasing, whining headache of an error message popping up. “Missing or invalid Smart Document”. Grrr… I’ll show him missing or invalid!


Hmpf. Well. As always, the answer came from a totally unexpected direction.
Well, a rather expected direction, but in an unexpected manner. The Troubleshooting document listed above mentions the following possible cause:



You have Microsoft Windows® .NET Framework 1.0 and Microsoft Windows® .NET Framework 1.1 installed on your computer. However, you accidentally configured the code access security policy using version 1.0 of the .NET Framework instead of 1.1. Correct this by using the .NET Framework 1.1 to grant Full Trust permissions to the assemblies. Remember to delete the previous settings.


I have never had 1.0 installed on this machine, so I didn’t suspect a thing – but then I remembered that I *DID* install the 2.0 framework a couple of weeks ago, trying to get Whidbey to run here. (Unsuccessfully, I might add). Even though I built my SmartDoc project in Everett linked to Framework1.1, and I set my code-access permissions in the Configuration tool for Framework1.1, and everything was supposed to be side-by-side and fine and dandy – and nevertheless, once I uninstalled the 2.0 framework things started working immediately.


My guess is Word tried to load the managed Smart Document DLL using the 2.0 DLLs. Does this make sense? Is there any reason this happens? Any way to stop it? Do I need to specify explicit binding rules in WINWORD.EXE.config to get it to bind properly when both 1.1 and 2.0 are installed?

Word and Excel – A Wor(l)d Apart

Technically, the same smart document can apply to both Word and Excel. In practice, if you want your smart-doc to actually DO something, chance are half your code will be hacked and kludged to work for both. For example:



  • The XML model is different for the two applications.

  • To get Smart Document controls to respond to the entire document in Word, you’ll use the element #actionPertainsToEntireSchema.
    In Excel, you’ll bind to http://schemas.microsoft.com/office/smartdocuments/2003#default.
    Needless to say, this is not documented anywhere.

  • Assuming we’ve accepted the fact that an ActiveX control bound to one of these generic elements in entirely inaccessible from code (Here, about 80% of the way down. Running a search for “pertains“ should do it), we still can’t use the same code for Word and Excel even for standard elements: XML elements in word are of type Microsoft.Office.Interop.Word.XMLNode. Excel’s XML model is different, and uses element-mapping to cells or ranges. Even if we do get a node, to get the Smart Tag Action from it we’ll use Microsoft.Office.Interop.Word.SmartTag, while Excel has Microsoft.Office.Interop.Excel.SmartTag. Similar? Very. Compatible? Not necessarily.

In short – if you gotta support both Word and Excel, I would suggest having two different Smart Document solutions. It would seem like the less elegant solution, but when you start seeing every function broken up into huge, distinct if/else blocks, you can tell that something’s just not right.

Who Am I?

As we probably know, the most popular activity involving the Active Directory for the developers amongst us is to check for authorization. We get a username from the context/environment/browser, and now we wanna check if the user is allowed to do whatever. We may use the role-based security solutions provided by COM+ or .NET or whatever solution we want, and they are nice and fine and good, but there comes a time in every programmer’s life when he must lay down the line and say “That’s it. I’m going in there. Just me and System.DirectoryServices. If I’m not out in 30 minutes, contact my administrator”.


So first, a word of warning – System.DirectoryServices is a COM wrapper. It is NOT managed code. Windows has a lovely library for accessing the AD and other LDAP providers, and had it for years – ADSI in its various version and incarnations. A nice API, all in all. DirectoryServices in the BCL is just a set of wrappers over ADSI, not a brand-new implementation of LDAP. This means that there is a lot of COM interop going on in the background. This shouldn’t be too worrisome, since COM interop isn’t a bad thing, but it is important to dispose what you instantiate and make sure you don’t leave things lying around.


There, having established that, we will quickly learn to create a DirectoryEntry for our user (either directly or using the DirectorySearcher – no need to elaborate there (though I can, if people ask me to)) and we can now check the properties for the “memberOf” property and see what groups he is a member of. This works well, except that we only get the groups we are DIRECT members of. If we want a complete list of nested groups, we have to start recursing and iterating, and this takes a LONG time. Especially when we have a lot of groups.


So what we do? We use a little-known and barely-documented feature of our Windows authentication architecture.
You see, when we try to access a network resource, for instance, the resource has to know whether we are authorized or not. It does this by receiving from the client (our user) a set of security credentials that contain the Security IDs (SIDs) of all the groups we belong to, whether directly on indirectly – we can’t have Windows recurse through the AD whenever we try to access some folder, right?


So we’ll use the same mechanism. Among the many properties available for a DirectoryEntry object in the ActiveDirectory is a property called “tokenGroups” which contains a list of all SIDs of all security groups the user belongs to. This list is flat, since it is used for direct authorization only, not for complex rules inherent in our organizational hierarchy. This list of SIDs can then be translated into group names for our enumerating convenience, or perhaps we should just get the SID for the group we are checking and see if it is the list.


 A few points of interest:


1) Since the tokenGroups property is relatively heavy, it is not automatically loaded when we create a new DirectoryEntry. To do so, we will call the entry’s RefreshCache() method to explicitly load that property:


DirectoryEntry de = new DirectoryEntry(my LDAP path);
de.RefreshCache(new string[] {“tokenGroups“});


2) Once we loaded the property, it is represented as an array of byte arrays, so we will get it like this:


PropertyValueCollection tg = de.Properties[“tokenGroups”];
foreach (byte[] SID in (Array)tg.Value)
{
   // Translate the SID, or whatever.
}


1) To get a group name from a SID (which is basically an array of bytes), we’ll have to do a little Win32 interop using the LookupAccountSid function.


enum SID_Types
{
   SidTypeUser = 1,
   SidTypeGroup,
   SidTypeDomain,
   SidTypeAlias,
   SidTypeWellKnownGroup,
   SidTypeDeletedAccount,
   SidTypeInvalid,
   SidTypeUnknown,
   SidTypeComputer
}


[DllImport(“advapi32.dll”)]
private static extern bool LookupAccountSid(
       string lpSystemName,
       byte[] lpSid,
       StringBuilder lpName,
       ref int cchName,
       StringBuilder lpReferencedDomainName,
       ref int cchReferencedDomainName,
       ref SID_Types peUse
);


private string getNameFromSID (byte[] SID)
{
  
int
GroupLength = 256;
   int DomainLength = 256;
   StringBuilder Groupname = new StringBuilder(GroupLength);
   StringBuilder Domainname = new StringBuilder(DomainLength);
   SID_Types AccountType = SID_Types.SidTypeUnknown;
   LookupAccountSid(null, SID, Groupname, ref GroupLength, Domainname, ref DomainLength, ref AccountType);
   return Domainname.ToString() + @”” + Groupname.ToString();
}

Disposable Impersonation

Impersonating a user in .NET isn’t quite as simple as it could be. It’s really easy if you you already have a WindowsIdentity or WindowsPrincipal in hand, but to get a handle on an arbitrary user you have to resort to P/Invoking the LogonUser and DuplicateToken APIs to get an impersonation context given only a username and password.


Easy enough to wrap in your own function, if you need to use it, but then you have to remember to Undo() the ImpersonationContext when you’re done. Not that bad, but could be neater.


So the solution I came up with today is this:


Create a new class, call it ImpersonationContext, and add our Impersonation code in the constructor – pass the username/password as parameters. The WindowsImpersonationContext object we’ll save in a private variable.
Next we’ll implement IDisposable in our class and call the WindowsImpersonationContext.Undo() in the Dispose method.


What does this give us? A rather clean and readable way to run a block of code using a different security context, with automatic reversal of the impersonation when we’re done.


using (new ImpersonationContext(username, password, domain)
{
      // Run my impersonated code.
}


(Sorry for not bringing actual code – this was written on a closed network so I don’t have the sources available)

Office 2003’s disappearing-reappearing PIAs.

Just in case someone else is running into this problem:


If you’re trying to install Office 2003 Professional and want to make sure you install the PIAs as well, you go to the setup components list and look for .NET Programmability Support and are surprised when you just can’t find it – make sure you have the .NET Framework 1.1 already installed on the computer before you install Office. It won’t show up on the list, and won’t get installed, if the framework isn’t already there – and it’d be a shame to go and reinstall Office now, wouldn’t it?

Ghost Files


For some obscure reason, Windows Sharepoint Services allows me to fetch a non-existent file.


Consider this code:
SPSite site = new SPSite(site_url);
SPFile file = site.RootWeb.GetFile(file_url);


This code will return an SPFile object regardless of whether file_url points to an existing file or not.
To find out if the file exists, you need to call the file.Exists() method.
Even if we ignore the fact that it’s against the common practice of throwing exceptions for these situations, it still behaves oddly.


You would expect that the properties for a non-existent file would be null, or at least throw exceptions – and indeed, most of them do throw (more on that later), but some of them DON’T – properties like the Name and URL of the file return values that are derived from the original URL I passed it, even though they do not, in fact, point to an existing file.


And the other properties DO throw, but they either throw a System.ArgumentOutOfRangeException, which makes absolutely no sense, or else a SPException specifying that the document library doesn’t exist. The same property can throw a different one of the above exceptions, depending on whether the URL we gave it points to a non-existing file in an existing doc library, or to a doc library that doesn’t exist at all.


It’s a mess, I tell you – a mess.

Programmatically creating an IIS Application

Using System.DirectoryServices to manage IIS servers and directories is a long-established practice (or is it? Should I elaborate on that? Write a short article?), but I ran into a problem today when I wanted to turn an ordinary directory under IIS (6.0) into an Application. It seems that among the many Metabase properties available, the only one relevant is AppRoot, and the MSDN strongly urges one not to touch it since it is managed internally.


The alternative is simply to access the IisWebDirectory object (part of the IIS ADSI Object model) and call the AppCreate (or AppCreate2, or AppCreate3 under IIS6) to create the application.


Very nice and simple, except that I’m working with a DirectoryEntry object and don’t have AppCreate to save my life.


The thing to do in this case is to access the NativeObject property of the DirectoryEntry, cast it into an IisWebDirectory, and call the method – but for some reason, I don’t seem to have that class registered anywhere. The IIS Adsi object model (as added via References -> COM -> Active DS IIS Extension or Active DS IIS Namespace) don’t seem to have IisWebDirectory or IisVirtualWebDir defined, which means I am quite stumped. Looked around for it, in the Registry and other common COM hangouts, but couldn’t find any reference.


 


My final solution was to simply bypass the problem using direct means – reflection:


DirectoryEntry vdir = new DirectoryEntry (“IIS://localhost/W3SVC/1/Root/MyVDir”);


Object nativeObject = vdir.NativeObject;


native.GetType().InvokeMember(“AppCreate3”, BindingFlags.InvokeMethod, null, native, new object[] {2, “DefaultAppPool”, false}); // 2 means to use an Application Pool.


 


Is there a slightly neater solution? Does anyone know where the IisWebDirectory object is hiding?

The Joy of VSTO.

At last, I have a glowingly positive, happily cheerful and satisfyingly pleased entry instead of my usual rant.


I installed the Whidbey Beta 1 build yesterday, and immediately started checking out the beta of VSTOv2. After the grief I went through with the Smart Documents API, I wanted to see if the much-anticipated ActionsPane interface gives me a decent replacement.


Two hours of API-learning and online help-reading later, I had in my hands a rather neat (though not very innovative) CD catalog template, with a fully functional and interactive task-pane

The API Maze

A reminder to those lost:


The SystemInformation class, containing many useful tidbits of information like the running computer name, number of monitors and whether we’re on a network, resides in System.Windows.Forms of all places.


Sure, most of the information there is GDI and Forms related, but why do I need to include and reference this in a console application if all I want is the name of the computer? A more generic SystemInformation should be sitting directly under the System namespace.


VB 2005 has the My namespace for easy access to such information – but this is basic enough to be CLR-wide.