# Friday, May 22, 2009

Curse you PublishingPageLayout!!!!!! Or the discovery of a wonderful "feature" of Sharepoint 2007

*** Please Note that this is done on a "publishing" site, it may be that this issue (I mean 'feature') does not occur on other types of sites. But I doubt it.
 
For those of you unfamiliar with Sharepoint, it combines Master Pages (for overarching look and feel or every page in the site) with Page Templates (which help define how a specific page should be layed out but not the content itself). One way to think of it is that each page in Sharepoint has three levels:

  1. The master page is the highest level and applies to all pages (unless overriden by a page or subsite)
  2. The page template layout determines what 'type' of page (ie web parts page, or article page, or welcome page, or ...)
  3. The actual page content.

Remeber that Master Pages can have parent Master Pages. Page Template Layouts are not considered Master Pages by Sharepoint, but for those of you familiar with the concept of parent/child Master Pages, Page Template Layouts act in the manner similar to a Child Master Page. 

Using Page Template Layouts works wonderfully on Sharepoint unless you have ...

  • more than one server, or ...
  • at least one extension to the server, or ...
  • you move to a new server, or ...
  • you move files from one server to another (via publishing, Sharepoint Designer, manual, etc).

In other words, unless you have a single url to use which never, ever changes, you may be in for trouble.
 
Why? Well it's all relative. Or more specifically it is NOT relative, it is absolute.
 
Basically whenever you create a page it prepends the page layout template section with the URL that you created the page on, turning it into an absolute reference. In other words if you take a look at your page (ex by downloading a copy) you will find that there is a section that reads like this:
 
<mso:CustomDocumentProperties>
... stuff here
<mso:PublishingPageLayout msdt:dt="string">http://theurlthatyouused/_catalogs/masterpage/PageFromDocLayout, Article page with body only</mso:PublishingPageLayout>
... more stuff here
</mso:CustomDocumentProperties>
 
Note that it has prepended the url, whether it be interal - http://myserver:8080 - or external one - http://www.myfriedmind.com.
 
So what does this mean for you? I have good news and bad news.

  • The good news: even if the URL of the page is different (eg calling the page from http://myserver:9090 and not http://myserver:8080 if you extended your site) the page will still show up. Even though the absolute url is different it still pulls it from the database as if it was a relative url. Strange but true.
  • The bad news: You will not be able to edit the page settings from other than the original URL.

If you do, expect this to appear:


 
WHA????????????? Where did that come from? Did someone else modify it? Nope.
 
After choosing Exit Without Saving you might get it to work. Maybe.
 
You might also get this error if you have a webpartpage and decide to edit one of the webparts. Keep clicking on choices until one of them works...
 
Does this remind anyone of being lost in the caverns of Adventures of Zelda? You are in a maze of twisty little passages, all alike...
 
The best is if you try to modify the Page Settings on such a page...

*BOOM*

What has happened is that Sharepoint is looking at the PageTemplateLayout property of the page (which is an absolute reference). If the Server Name and/or Port that you are editing from do not match that exactly then Sharepoint assumes that someone has modified the page between when you started editing and when you try to save it.

Again, one of the ways that Sharepoint tries to determine if your copy is 'dirty' (ie someone has made a change between when you first tried to edit and now) is that it looks at the PageTemplateLayout property. If there is not an exact match it rejects it. If you try to modify the Page Settings it explodes.

  • Scenario #1 - you copy a page from a different URL using Sharepoint Designer - it simply copies the file, including the now INCORRECT URL.
  • Scenario #2 - You are moving your sharepoint to a bigger, faster server (or you are restoring). The new server has a different name...
  • Scenario #3 - You are publishing the pages out to a different front end server but want to be able to edit it from there...
  • Scenario #4 - you are extending a web app to be used on the internet, and you want both internal and external people to be able to edit the same pages, but using different URLs...

etc, etc, etc...
 
What is the solution? Well, there are a couple. One is to use this nifty addition to STSADM - http://stsadm.blogspot.com/2007/08/fix-publishing-pages-page-layout-url.html. Gentleman Gary has done a wonderful job creating a flexible way to repair these beasties. The only caveat - it is an incomplete fix. Basically it still uses an absolute URL, it just replaces the wrong one with the correct one (see his note at the bottom in response to my question). It will fix #1 and #2 above (although if you move it again you will have to rerun it again), but it does nothing for #4. I am not sure about #3 <g>.

Again, note that this does not fix the issue with multiple access, only where a page has been moved. However, if it is simply a matter that you have moved your pages to a different server than this is a pretty rocking solution.
 
I have to deal with #4 after doing #2, so what do I do?

Initially I personally download each file as I uncovered the issue and manually removed the absolute url from the text and replaced it with a relative url and then uploaded the file. I expected a handful to show up, but the number kept increasing. In addition it was a pain for our staff. Sometimes I could spot it after they had made a non-blowing up modification by checking the nvarchar14 in the content database (Select tp_id, nvarchar14 from AllUserData Where nvarchar14 LIKE 'http://%') , but sometimes I would only catch if when they called me to complain.

Btw, if you are thinking that you can just modify the nvarchar14 instead of extracting and modifying the page, it does not work. It will be overwritten once another change is made to put it back to the absolute url. Trust me.

My solution ultimately was to write some code in c# that:

  1. extracted each page
  2. checked it out
  3. inspected the PageTemplateLayout text for an absolute URL
  4. replaced it with a relative URL if that was the case
  5. uploaded the page
  6. checked in the page
  7. published the page
  8. approved the page
I may publish that code in another blog on the off chance anyone is curious about how to do such a thing. This solves Scenario #2 and #4 for me.

Long term? Microsoft???? 

Maybe this is part of the design, or maybe they just missed this. I am just hoping they will fix it. There appears to be absolutely no loss of functionality with a relative reference and even thought there *might* be a case where a page template is housed on another site (and hence a different URL) this is far too serious an issue to sweep under the carpet.
 
I will enter more on another PageTemplate issue in another blog...

# Wednesday, May 20, 2009

Ajax history and the Memento pattern

Note: special thanks to Thomas who catches my error in using QueryString vs url hash - I have made the appropriate changes in this entry...

Part 1 - Introduction
Part 2 - Basic Example
Part 3 - Complex Example
Part 4 - Final Notes
Bonus - Ajax History and the Memento Pattern
Extra Bonus - Issues with Opera

Recently while watching a geekspeak on Ajax history it struck me that it reminded me strongly of a Memento pattern. I mentioned this to Glen Gordon and he recommended I write a blog on it, so without further ado...

First off, a little background. Maintaining history has been one of the holy grails of Ajax since it began. It is a wonderful feeling to produce a zippy, intiutive site that has fantastic UI, but once someone hits the "back" button everything goes down the tube. The reason is simple, browsers were created in the time before, even before Javascript, much less XML. Back then the "back" button, or any sort of history action, brought you back not to your last action but to the last page that fully loaded.

To give a parallel, for anyone who has used the "undo" command in whatever program, what it does is "undo" the last action that you did. Imagine typing for hours in Word, clicking "undo", and having it wipe out EVERYTHING that you had just done, not just the last little action. No one would want that, but this is what the "back" key effectively did by default. Hence, the quest to maintain Ajax history. Wouldn't it be nice if the "back" key, or other "back" command acted as an "undo" vs a true "back to the previous page"?

In addition, what if after working on a page (say a maps page) that uses Ajax, you want to send someone the link. The option is to use a URL, but since Ajax by default modified the page but not the URL, all that work would be useless. You would think you were sending them a map to your cabin by the lake, they would simply get the default map of the world. Not good.

There have been ways to resolve this even before .net 3.5 came out. One merely has to look at some of the larger Ajax enabled sites (such as google maps) to see this, but this is such a constant and complex requirement in almost any Ajax development that it is only logical that someone develop a abstractive pattern to simplify and standardize it. The 3.5 framework does this, one of many reason it makes me squeal like schoolgirl. But what is especially fun to look at is how it does it, specifically what pattern was chosen. The pattern, which is extremely logical to choose, is akin to the Memento pattern.

I won't go into Design Patterns here. If you are not sure, follow this link, buy the book, get the poster. I want to focus on one pattern, the Memento pattern and how it relates to Ajax history in the hopes that it will illumine your coding.

The Memento pattern requires two players, commonly refered to as the Originator and the Caretaker. To clarify the interaction:

  1. The Caretaker is going to change the Originator in some way but the Originator needs to be able to undo the change.
  2. The Caretaker receives a small "memento" object and stores it (hence the Caretaker name).
  3. The Caretaker then changes the Originator.

If the change needs to be undone:

  1. The Caretaker returns the "memento" object to the Originator.
  2. The Originator uses the "memento" object to undo the change.

But it is also critical to understand about the "memento".

  • The "memento" is an opaque object, meaning the Caretaker can not change it
  • The "memento" is small, it is not the entire Originator, merely the minimal information needed to rollback state
  • The Originator is the one who packages and unpackages the "memento"
  • The Originator is the one who rolls back state (ie, the Caretaker does not modify the Originator to roll it back)

So how does this apply to how .Net does Ajax history? In short, .Net uses the URL of the page as the "memento", the History as the Caretaker, with the code you write in the framework acting as the Originator.

  1. The Ajax action is going to change the webpage someway.
  2. The current hash is already stored
  3. The Ajax action makes the change and alters the hash

If the change needs to be undone:

  1. The hash (in the URL) that was from the previous page (the rollback state) is returned.
  2. The code that YOU wrote rollsback the state.

In this case the memento has these characteristics

  • The "memento" is a url which is unchanged by the Caretaker (browser)
  • The "memento" is small (you should make sure of this)
  • The framework writes the URL that you need and allows you to access it
  • Your code is what rolls back state

So, what does this mean? Basically the key point is that you can apply the practices that you have in place for working with the Memento pattern to working with Ajax history. Chief among these is that the "memento" should be small! Do not try to squeeze too much into your URL. It is not pretty and there are limits! Instead pare down to the minimum of what you need to be able to restore state.

One major bonus is that this not merely allows rollback, but enables your users to use URLs to return an Ajax-enabled page on which certain steps have already been taken. This is AMAZING!!!!!!! And compared to how we used to have to do it, it is like a walk in the park!

 

# Monday, May 18, 2009

Sharepoint Backup Permissions or SqlException: Cannot open backup device '\\myserver\backups\spbr0000\00000024.bak'. Device error or device off-line

For those of you who are using Sharepoint's 'built-in' backup feature, you probably already know that one of the biggest problems with it is that you can not automate it directly. In order to do that you need to used AT ->
AT 01:00 /every:1,2,3,4,5,6,7 cmd /c "stsadm.exe -o backup -directory file://myserver/mybackupshare -backupmethod full"
 
This is not a showstopper. It works. However unless you have the correct perms it will fail. Or, to be more specific, the Sql Service Account needs to be able to read/write to that share. Again, this is the account that the Sql Service runs under. Hopefully a domain account.
 
Also, if you are going to run it from the Sharepoint Central Administration Website, the account you are logged in with needs those perms as well.
# Friday, May 15, 2009

Activesyncy 2007, Isa 2006 and 0x85010014 error

When we migrated to x07, our Activesync started throwing the ol' 0x85010014 error. If you search for this online you will discover that it is one of the more common errors and there are about a dozen ways to fix it.

None of them worked. Everything was as it should be. I was completely flummoxed, until I stumbled across a post in a forum where someone said that he had solve the problem by changing the ISA "To" tab on the rule to cause the Request to come from the Original Client (see pic below). Tried that and voila, it worked!

Now I am not sure if there was not something else going on and I have not tried to change the rule back (my users get antsy if their phone stops synching), so take this with a grain of salt. Still, if you are pulling your hair out, try this:

# Thursday, May 14, 2009

Sharepoint 2007 and Ajax

Recently someone asked about using Ajax on Sharepoint. I should note that a number of resources are out there on this which I used, so - as brilliant as I am <g> - the credit for this has to go to those who have gone before...

Like a lot of people I have been working on both since their betas, so whether or not I should install Ajax on Sharepoint was a no-brainer. Unfortunately, when Sharepoint 2007 first came out it was the very end of 2006 and Ajax was not officially released until January 23, 2007. As a result, in order to utilize Ajax on Sharepoint (pre-SP1) you needed to jump through a whole host of hoops. This is not to say it could not be done, merely that it was a cumbersome and painful process.

Step 1: Install SP1 on Sharepoint

Luckily the folks on the Sharepoint team made that a priority, and even though we had to wait a bit for it, they did include 'Ajax support' in SP1. So the first step to using Ajax on Sharepoint 2007 is, quite simply, install Sharepoint 2007 SP1. You can do it without SP1, but really, why would you?

Note that simply installing SP1 does not automatically turn on Ajax (there are more steps) but it removes some of the bugs that popped up.

Step 2: (Recommended): Install Asp.Net framework 3.5 SP1

The next thing that I strongly suggest is that you upgrade to .Net 3.5 (sp1). Why? Simply because if you are going to be doing any Ajax you will want to.

  • It includes it directly in the framework, no need to install it separately
  • It has awesome debugging - you will still use Fiddler, but less often
  • Intellisense
  • Much, much easier means of creating a 'tracking history' of Ajax calls

If you decide to install .Net 3.5, note that you will not see a Web Service Extension for it in the IIS Manager. This is not a flaw, this is because 3.5 is an extension of 2.0. Let me state that again - if you install 3.5 you will actually use 2.0 as the .Net version.

Doing these two steps does not mean that Ajax is automatically up and running, just that the necessary dlls have been put into the GAC.

One final note - if you install 3.5 you will be using version 3.5.0.0 of System.Web.UI.* in a number of your web.config entries. If you are using an earlier version, make sure to check the GAC and find out what the specific version is. Most of the rest of the steps involve modifying your web.config, so make sure you know your System.Web.UI.Extensions version before you being

Step 2a: Make a back up of your web.config

We are all good techies here, right? Which means we are all paranoid, right? Never, ever modify your web.config unless you have a rollback copy at hand. Remember, paranoia is a job-requirement.

Step 3: Add the system.web.extensions sectionGroups to the configSections of your web.config

If you are not familiar with Sharepoint, this will be located in your c:\inetpub\wwwroot\VirtualDirectories\wss\{your site's port here}. So if you have your Sharepoint on port 8080 your web.config will be located at c:\inetpub\wwwroot\VirtualDirectories\wss\8080. Remember to make a copy of it like a good, paranoid nerd. Also, I tend to remark what I am adding and why to my web.config, but you don't have to.

Add this in the <configSections> portion, which should be at the top. You will already see other <sectionGroup... > entries there, just add this one at the end (above the </configSections> entry), wrapped for readability...

<!-- ======= Added for Ajax start ====== -->
<
sectionGroup name="system.web.extensions"
          type="System.Web.Configuration.SystemWebExtensionsSectionGroup,
          System.Web.Extensions, Version=3.5.0.0, Culture=neutral, 
         
PublicKeyToken=31bf3856ad364e35">
     <
sectionGroup name="scripting" 
             
type="System.Web.Configuration.ScriptingSectionGroup,
             
System.Web.Extensions, Version=3.5.0.0,  
              Culture=neutral, PublicKeyToken=31bf3856ad364e35
">
         <
section name="scriptResourceHandler"
               
type="System.Web.Configuration.ScriptingScriptResourceHandlerSection,
                 System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                 PublicKeyToken=31bf3856ad364e35
" requirePermission="false" 
                
allowDefinition="MachineToApplication" />
         <sectionGroup name="webServices"
                
type="System.Web.Configuration.ScriptingWebServicesSectionGroup,
                 System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                 PublicKeyToken=31bf3856ad364e35
">
             <
section name="jsonSerialization"
                    
type="System.Web.Configuration.ScriptingJsonSerializationSection,
                     System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                     PublicKeyToken=31bf3856ad364e35
" requirePermission="false"
                    
allowDefinition="Everywhere" />
            
<section name="profileService"
                   
type="System.Web.Configuration.ScriptingProfileServiceSection, 
                    System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                    PublicKeyToken=31bf3856ad364e35
" requirePermission="false"
                   
allowDefinition="MachineToApplication" />
            
<section name="authenticationService"
                   
type="System.Web.Configuration.ScriptingAuthenticationServiceSection,
                    System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                    PublicKeyToken=31bf3856ad364e35
" requirePermission="false"
                   
allowDefinition="MachineToApplication" />
          </
sectionGroup>
     </
sectionGroup>
</
sectionGroup>
<!--
======= Added for Ajax end ====== -->

Step 4: Add System.Web.Extensions as a "SafeControl" to your web.config

SafeControls are those assemblies that should be considered "Safe" by Sharepoint. Do a quick find for <SafeControls> and you will see a whole lotta them. Simply add this line to the bottom, above the </SafeControls>.

NOTE!!! If you are using a different version than .net 3.5, make sure that you verify what version it is in the GAC. This needs to be specified in the "Version" section or Sharepoint will throw an error. Because I am using 3.5, my version is 3.5.0.0

<!-- ====== Added for Ajax  ====== -->
<
SafeControl Assembly="System.Web.Extensions, Version=3.5.0.0,
       
Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
       
Namespace="System.Web.UI" TypeName="*" Safe="True" />

Step 5: Add the needed "verbs" to your <httpHandlers>

Note that this is NOT the <httpModules> section. There should already be some entries there, so just stick this at the end, above the </httpHandlers> line....

<!-- ====== Added for Ajax start ====== -->
<
add verb="*" path="*.asmx" validate="false"
       
type="System.Web.Script.Services.ScriptHandlerFactory,
        System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
        PublicKeyToken=31bf3856ad364e35
" />
<
add verb="*" path="*_AppService.axd" validate="false"
       
type="System.Web.Script.Services.ScriptHandlerFactory, 
        System.Web.Extensions, Version=3.5.0.0,
        Culture=neutral, PublicKeyToken=31bf3856ad364e35
" />
<
add verb="GET,HEAD" path="ScriptResource.axd"
       
type="System.Web.Handlers.ScriptResourceHandler, 
        System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
        PublicKeyToken=31bf3856ad364e35
" validate="false" />
<!--
====== Added for Ajax end ====== -->

Step 6: Add the <controls> reference to the <pages...> section of the web.config

This will enable your pages to recognize the controls that come with Ajax (such as the ScriptManager, etc). Locate the <pages ...> section and add this at the end above the </pages> entry. Again, keep an eye on the Version.

<!-- ====== Added for Ajax  ====== -->
<
controls>
    <
add tagPrefix="asp" namespace="System.Web.UI"
         
assembly="System.Web.Extensions, Version=3.5.0.0,
          Culture=neutral, PublicKeyToken=31bf3856ad364e35
" />
</controls>
<!-- ====== Added for Ajax  end ====== -->

Step 7: Add this at the very end of your web.config, just above the </configuration> line

Of course you could stick it somewhere else, but let's be neat here... This is the section that first thing that you put into your web.config (the <sectionGroup...> referenced. There is documentation out there on sectionGroups, and they are very handy, if you get interested. I have left a lot of commented lines in there for if you need them. And, once more repeat after me, if you are not using 3.5, check the Version!

<!-- ====== Added for Ajax start ====== -->
<
system.web.extensions>
    <
scripting>
        <
webServices>
            <!--
Uncomment this line to enable the authentication
                service. Include requireSSL="true" if appropriate.
-->
                <!--
<authenticationService enabled="true" requireSSL = "true|false"/> -->
             <!--
Uncomment these lines to enable the profile service.
                   To allow profile properties to be retrieved and modified 
                   in ASP.NET AJAX applications, you need to add each property
                   name to the readAccessProperties and 
                   writeAccessProperties attributes.
-->
                <!--
<profileService enabled="true"
                           readAccessProperties="propertyname1,propertyname2"
                           writeAccessProperties="propertyname1,propertyname2" />
-->
 
       </webServices>
        <!--
<scriptResourceHandler enableCompression="true" enableCaching="true" /> -->
   
</scripting>
</
system.web.extensions>
<
system.webServer>
    <
validation validateIntegratedModeConfiguration="false" />
        <
modules>
            <
add name="ScriptModule" preCondition="integratedMode"
               
type="System.Web.Handlers.ScriptModule, 
                System.Web.Extensions, Version=3.5.0.0, Culture=neutral, 
                PublicKeyToken=31bf3856ad364e35
" />
        </
modules>
        <
handlers>
            <
remove name="WebServiceHandlerFactory-Integrated" />
            <
add name="ScriptHandlerFactory" verb="*" path="*.asmx"
                 
preCondition="integratedMode"
                 
type="System.Web.Script.Services.ScriptHandlerFactory,
                  System.Web.Extensions, Version=3.5.0.0, Culture=neutral, 
                  PublicKeyToken=31bf3856ad364e35
" />
            <
add name="ScriptHandlerFactoryAppServices" verb="*"
                 
path="*_AppService.axd" preCondition="integratedMode"
                 
type="System.Web.Script.Services.ScriptHandlerFactory,
                  System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                  PublicKeyToken=31bf3856ad364e35
" />
            <
add name="ScriptResource" preCondition="integratedMode"
                 
verb="GET,HEAD" path="ScriptResource.axd"
                 
type="System.Web.Handlers.ScriptResourceHandler,
                  System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                  PublicKeyToken=31bf3856ad364e35
" />
        </
handlers>
</
system.webServer>
<!--
====== Added for Ajax  end ====== -->

Step 8: Add a ScriptManager to your Master Page

You can always do it dynamically, and I always check in my code to see if it exists before whatever webpart I am writing loads, but it is much simpler just to add it on your Master Page. Just stick it below the <form id="Form1"... > entry :

<asp:ScriptManager runat="server" ID="ScriptManager1 />

Please note that there are a LOT of other goodies you can add to your ScriptManager, especially in .net 3.5, so feel free to expand it. Just make sure it is in there.

Step 9: Write Ajax pages (or webparts).

If you have followed these directions you should be ready to roll. I have not included how to add the AjaxControlToolkit, but if you are this far it is fairly a no-brainer. Add the .dll to the GAC; add the assembly reference to the web.config 'assemblies', add the namespace entry to the web.config 'controls' section and you are ready to rock! 

Final NOTE!!! UpdatePanels on Pre-SP1 ...

You may need to add the following code to get UpdatePanels to work properly, just put it in your Master Page

<script type="text/javascript">
   
_spOriginalFormAction = document.forms[0].action;
   
_spSuppressFormOnSubmitWrapper=true;
</script>

# Tuesday, May 05, 2009

Exchange 2007 and Exchange 2003 and the eternal mail loop

We recently added x07 to our domain, doing the standard migrate from x03 over to a new box. Everything went surprisingly smoothly except for a few 'glitches' of which I will write more in different blogs.

What I want to talk about today is an issue that arose due to the requirement that our outgoing email continue to be sent through our x03 system and not our brand, spanking new x07. I won't go into the reasons involved, but suffice to say we had our marching orders.

One of the beauties of all the work MSoft has poured into x07 is how remarkably it plays well with others, or at least with x03. We moved the mailboxes and it did not matter whether incoming mail hit the x03 or the x07, it got delivered correctly. The reason for this is that x07 automatically a routing connector to the old x03 system that routes it to the x07 system (if boxes are not local). This is wonderful, but it also tripped me up.

I had assumed that the solution would simply be to add a Send Connector to the X07 box. Basically anything that was going to be routed via SMTP would be directed to the old x03 box. It was very simply to setup - go into the Exchange 2007 Management Console, go under Organization Configuration and thence into Hub Transport. Select the Send Connectors tab, right-click and select "New Send Connector...".

I won't go into how to create a new SMTP Send Connector, it is fairly straightforward -> give it a good name; specify that this is an "Internal" connector (we are all on the same team after all); add the address space of * and an appropriate cost (I initially selected 20 but just make sure it is lower than what you have for the rule sending to your Edge server); select to route through a smart host and specify the X03 box as your smart host; I chose "None" for my authentication since this was for testing; and voila!

It initially appeared to work and then all of a sudden emails were getting hung up. Sending an email threw it into a void until eventually an NDR would be generated with the following line:

The following recipient(s) cannot be reached:

bob@bob.com on 4/25/2009 2:45 PM
A configuration error in the e-mail system caused the message to bounce between two servers or to be forwarded between two recipients. Contact your administrator.
<mail.mydomain.com #5.4.6 smtp;554 5.4.6 Hop count exceeded - possible mail loop

Oops.

The culprit, of course, is that each of the two systems, x03 and x07 have routers that send the email back and forth. They are so happy doing such a thing that they won't pass it out, just back and forth, back and forth, back and forth.

My initial thought was to see if I could modify the Routing Connector that was automatically added by x07 during the install. It had a weight of "1", which meant that it was THE rule. As a result, the rule that goes to the Internet is ignored (having been bumped up) and evertime the x07 system passes an outside email to the x03 system, the x03 system insists on passing it back. However, I know of now simple way to do this. Opening up the router in x03's System Manager results in the following error "Exchange System Manager version 8.0.30535.0 or greater is required to edit this object. See About dialog for version information". So that won't work. The Send Connectors section of the x07 Management Console does not show this. I am sure there is an easy way to do this (probably through powershell) but I did not find one.

So instead, went back to the x03 box and set the internet smtp route to ALSO have a value of "1". For some reason which I can not easily fathom, mail started to flow through the x03 box. What confuses me is how the x03 box, having TWO connectors of value "1" seems to select the correct one. Perhaps it automatically selects the earlier one, or perhaps it is something as odd as the fact that the x03->Internet connector comes alphabetically before the x03->x07 connector.

What ever the solution - I would like to close with that old, old MSoft test answer which sadly disappeared a while ago - "Solution should not works, but appears to".

 

# Friday, May 01, 2009

We Recently Installed X07 And Migrated Over From Our X03 Box To Encounter An Odd Surprise Among Many A Number Of The Mailboxes

We recently installed x07 and migrated over from our x03 box to encounter an odd surprise (among many) - a number of the mailboxes were listed as "Linked Mailboxes". A Linked Mailbox is a mailbox which is "linked" to a foreign account (for example in another forest). The problem lay in the fact that we were not in a forest with other domains (any more) and all of the mailboxes had always been local to our domain.

There appeared to be no rhyme nor reason - the accounts did not depend on when they were created, nor what database they were in. It did not matter what OU, what DL, what anything. One person would be taken, another left behind (in the linking sense).

A bit of background before I reveal the answer so you might understand how this could happen. We were around in the old Exchange 5.5 days, using NT 4.0. Ah, the good ole days <g>. When we migrated to w2k and x2k we did it by creating a completely new domain within a forest housed elsewhere. Eventually, for political reasons, we ended up leaving that forest, which involved a whole lotta info I don't want to go into here. We moved them to w03 with x03. Now we were keeping our w03 domain/forest (for now) and simply upgrading x07.

Ah ha, you might say to yourself, clearly the mailboxes from the Exhange 5.5 days. Or the x2k days. Or the x03 days. Or something. Unfortunately for that theory while all of the mailboxes that were still 'linked' were from way back when some of the mailboxes created at that time were not 'linked'. I even delved into the address, since we were still porting around old x.500 ones from way back when. However, again, that was no distinguisher.

So I began to hunt through the AD properties and *AH HA* there it was - all the mailboxes whose msExchRecipientTypeDetails where 2, ie 'linked' (1 is 'user', 2 is 'linked', in 'legacy' it is not set), had an entry for 'msExchMasterAccountSid', but those with msExchRecipientTypeDetails = 1, ie 'user', did not.

Just to verify I ran a bit of the following code (c#) for a quick check, although you could also do a straight ldap outside of it:

String _tab = Char.ConvertFromUtf32(9);
using (StreamWriter _logFile = new StreamWriter(@"c:\results.txt"))
{
    using (DirectorySearcher _searcher = new DirectorySearcher(_ldapRoot))
    {
        _searcher.SearchScope = SearchScope.Subtree;
        _searcher.CacheResults = false;
        _searcher.PropertiesToLoad.Add("msExchRecipientTypeDetails");
        _searcher.PropertiesToLoad.Add("msExchMasterAccountSid");
        _searcher.PropertiesToLoad.Add("displayName");
        _searcher.PropertiesToLoad.Add("whenCreated");
        _searcher.Filter = "(ObjectClass=user)";
        SearchResultCollection _matches = _searcher.FindAll();

        foreach (SearchResult _match in _matches)
        {

            if (_match.Properties["displayName"].Count > 0 &&
                _match.Properties["msExchRecipientTypeDetails"].Count > 0)
            {
                _logFile.Write(_match.Properties["displayName"][0].ToString());
                _logFile.Write(_tab);
                _logFile.Write(_match.Properties["msExchRecipientTypeDetails"][0].ToString());
                _logFile.Write(_tab);
                _logFile.Write(_match.Properties["whenCreated"][0].ToString());
                _logFile.Write(_tab);
                if (_match.Properties["msExchMasterAccountSid"].Count > 0)
                {
                    _logFile.Write(ConvertByteToStringSid((byte[])_match.Properties["msExchMasterAccountSid"][0]));
                }
                _logFile.Write(_tab);
                _logFile.WriteLine();
                _logFile.Flush();
            }
        }
    }
}

The ConvertByteToStringSid was borrowed from this excellent entry -> http://www.codeproject.com/KB/cs/getusersid.aspx

Once I ran that I was able to look at my entries in Excel and verify that that was the case - boxes listed a 'User' did NOT have this entry and boxes listed as 'Linked' DID. Then it was simply a matter of hunting down more information.

It turns out that msExchMasterAccountSid hails as far back as x2k (at least). It is simply used to associate a mailbox with any 'well-known SID or external account'. If this is not used, the objectSid is used. Far more info can be found at Detecting and Correcting msExchMasterAccountSid Issues. This particular article references x03 but is undoubtedly still applicable to x07.

So what is the solution?

More hunting uncovered that a number of people were having success by simply disabling and reconnecting - http://www.fots.nl/index.php/archive/how-to-convert-linked-mailbox-to-user-mailbox/ which I find far more preferable than mucking around in AD. If you have a LOT of mailboxes that you need to de-link, I do not suggest using the Sample Script (http://technet.microsoft.com/en-us/library/bb123636(EXCHG.65).aspx) since that applies to x03 and will probably miss some critical AD schema mods), rather use powershell to disable and reconnect them.

Addendum: if you do disable the mailbox and it does not show up in the "Disconnected Mailbox" you can run the Clean-Mailboxdatabase command. Syntax: Clean-MailboxDatabase "serverName\storageGroup\mailboxDatabase"
Ex: Clean-MailboxDatabase "exMaster\Primary\Executive"

# Thursday, April 30, 2009

Installing ISA 2006 on Windows 2008

Can't be done.

Any attempt to install will be met with "This operating system in not supported..." Supposedly the next version of ISA will be happy, happy, joy, joy, but not this one.

Could there be a hackaround? Probably. Should you do it? Probably not. Hacks can leave doors open unless you are positive you know where all the moving parts are.

So, buck up and used the old w03. At least for now...

# Sunday, April 26, 2009

The Web Proxy filter failed to bind its socket to .... or why you can't have Virtual Server & ISA 2006 on the same box

I installed ISA 2006 on a new firewall box. Or rather reinstalled, since I had stuck the trial version on because I NEEDED IT UP NOW!!!!!!!!!!!!!!
 
Of course I had stuck on the trial version and then let it expire so our website was suddenly unavailable. My cohort Patrick spent many frurstrating hours trying to get SOMEONE at MSoft to give us a license key so we could reactivate it.
 
Meanwhile....
 
I downloaded the MSoft Virtual Server for Windows 2003 R2, whipped it on the box, installed the trial version of ISA 2006 (AGAIN), and got it up and running.
 
Don't try this at home. I mean you, Lee.
 
Anyway, that was dandy and finally, much later, Patrick finally got ahold of someone who let him know that the license key was already in the product that we download via EA.
 
Ahhhhh
 
So today I decide to install ISA 2006 for real, and shut down the Virtual Server. I do so, and discover that I no longer have a site. My web server is groovy, but my firewall is throwing hissy fits. Egads, horror in the land.
 
So I restart the Virtual server, shutting down the ISA 2006, and now IT does not work. Shut down the Virtual Server and try again with the real thing.
 
hmmm
 
A wee bit o troubleshooting later I figure out that the network cable is bad (although it DID appear to work on the screen, it just couldn't GET anywhere).
 
Problem one solved.
 
Now I reboot it (why not) and see the error above. Thank the heavens for Google - turns out that ISA 2006 refuses to run if you have IIS installed. Virtual Server (for administration) requires IIS to be installed. Hence the bloody battle.
 
Deinstall Virtual Server. Deinstall IIS.
 
*boom*
 
it is working and I am going home!