Welcome to MSDN Blogs Sign in | Join | Help

Hyper-V Manager

Wrote this tool when I was setting up my laptop to run Hyper-V.  I didn't want to have to open the Hyper-V manager and have a window running all the time, and was planning on using Remote Desktop Client to connect to the VM's so I could map drives and copy/paste in teh VMs.  The idea was to have something that sits in the System Tray that allows you to Start, Stop, Save State, and Pause the VMs running on the box.

I did some looking around as I had no idea how to manage Hyper-V programmatically.  Luckily, some blogs were around that indicated WMI would be the way to go

With that, I dug in.  The files are attached and feel free to do with them what you want.  Zip contains 2 folders.  Compiled EXE is in the Hyper-VManager folder.  The Visual Studio 2008 project is in the Hyper-VManager_Source folder.

When you launch the app, you will get a UAC prompt.  The app needs to run with admin access to get to Hyper-V objects.  You get the same prompts opening the Hyper-V MMC.  Here's a quick break down of the app and what it does.  It currently manages a single machine.  You can manage a remote machine by changing the .config file entry.  Change \\.\ to \\MachineName\ and you should be good to go (considering you have permissions to the remote machine).  You could extend the app to manage multiple machines, but would need to tweak the code to handle building out the appropriate menus.  Shouldn't be much code, but considering I'm not going to be using it, decided not to do the work :-)

When you launch the app, it displays a Hyper-V icon in the System Tray:

System Tray Icon

Right-click the icon and you get a list of the Virtual Machines on that machine with the current state of the machine.  In this case the VM is Running:

List of VMs

Select a machine and you get four options:  Start, Stop, Save State, and Pause.  These get enabled/disabled depending on the current state.  Since the VM is currently running, the option to Start is disabled.

Action Menu

Changing the state of the VM results in a balloon notification to let you know the machine is changing.  You get a second notification once the state change has completed.  You can change multiple machines, and will get the various balloons for the machines. For example, clicking Save State shows the following notification:

Balloon Status Notification Change

Now that the VM is in a saved state, the menu updates to show the current status and the other menu options are disabled.

Options Disabled Based on Status

Something I found while putting the notification piece together is that if you don't move the mouse or interact with the machine, the initial balloon will not go away after the InitialDuration property value.  Thought it was odd, but looks like it's by design as the idea is if you're not interacting with the machine, you may miss the notification, so they display it until you start working on the box again.

That's pretty much it.  Good luck!

Posted by Jorman | 0 Comments
Attachment(s): Hyper-VManager_Files.zip

Setting up laptop for SharePoint 2010 and SharePoint 2007

I'm starting to get my world setup to dig into SharePoint 2010.  To do this, I bough 8GB of RAM for my work laptop as I need to run both SharePoint 2007 and SharePoint 2010 for demos and testing out various scenarios.  I also work with a variety of customers, so will be working with both products for quite a while.  My laptop was originally running on 4GB of RAM with Windows 7, and Windows Virtual PC to host SharePoint 2007...I really like this setup.  The Windows Virtual PC integration with the host and wireless NIC works really well.  The VM is also pretty speedy on my laptop with 2GB of RAM allocated to it.  Since Windows Virtual PC doesn't support x64, and I wanted an all Microsoft solution with SharePoint 2010 in a VM, the initial plan was to setup Windows 2008 R2 with Hyper-V then run SharePoint 2010 and SharePoint 2007 in VM's hosted on my laptop.  I have Hyper-V setup on a couple machines in the office and it's great.  One box has Windows 2008 R2 Server Core with Hyper-V and the other has Windows 2008 with Hyper-V.

The first hurdle I hit was that I use wireless on my laptop 99% of the time.  Since Hyper-V doesn't support wireless, you have to use some workarounds.  Since I also use the laptop to VPN and we get policy restrictions pushed to us, I'm pretty limited on those options, so I went with the RRAS scenario where you setup NAT between the Primary OS and the VM's. This blog has a good walkthrough:  http://sqlblog.com/blogs/john_paul_cook/archive/2008/03/23/using-wireless-with-hyper-v.aspx   With this setup, my primary box could remote desktop into the VM's so I could map my laptop drive to the VM drive via RDP, but the VM's couldn't get DNS to the internet, so no direct downloading of patches, and I was very tied to RDP for moving files to/from the VM.  I tried the comment in the above blog about setting up the DNS role on the Primary OS, but that didn't do me any good.  I also hit issues where my Primary OS couldn't find the VM's when I was VPN'ed on my wireless NIC, so that was also a no go.  Considering wireless in Hyper-V isn't a supported scenario, I really wasn't surprised by hitting a bunch of weirdness here.

The other issue I was hitting was that my laptop was taking 5 minutes to boot up.  With Windows 7, it was taking about 1 minute.  With Hyper-V enabled, you can't hibernate or put the machine to sleep, so this was also a problem.  The events being logged all seemed related to networking (timing out binding IPv6, Hyper-v setting up an SPN), and not being a networking master, I didn't want to spend a ton of time troubleshooting.  I did try the big hammer approach of removing all the roles I had added previously and the boot time was still in the 5 minute range. 

The above two issues pushed me in another direction.  I decided to take a lot of the complexity out of the picture and go back to Windows 7 as the host.  I would run SharePoint 2007 in Windows Virtual PC that I was super happy with, and then run SharePoint 2010 on Windows 7.  Main concern here is the use of Beta software on my main box, and how cleanly the upgrade to RTM will work, but considering I'm also running the Office 2010 beta I'll just cross that road when the product ships.  Here's the MSDN article on setting up SharePoint 2010 on Windows 7 for development work.  I did change the steps to some extent in that I installed SQL 2008 separately.

http://msdn.microsoft.com/en-us/library/ee554869(office.14).aspx

Couple things with this setup:

  • If using local accounts (which I do), you have to run a PowerShell command to create the SharePoint 2010 Configuration Database.  See this blog:  http://sharepoint.microsoft.com/blogs/fromthefield/Lists/Posts/Post.aspx?ID=112
  • If you want to manage when SharePoint and SQL are running, you can use PowerShell to start and stop processes.  I put a script together that starts/stops IIS, SQL, and SharePoint on an as needed basis.  This way you don't have timer jobs running and taking up resources when you're not using the product.  The script is attached as StartStopSP2010.ps1.  To run the script, you pass in a single parameter:
    • Stop SP2010  -->  StartStopSP2010.ps1 stop
    • Start SP2010  -->  StartStopSP2010.ps1 start

I created 2 shortcuts to manage this.  The shortcuts point to PowerShell.exe and pass in the above commands.  This allows me to pin the shortcut to the Start Menu, or the Windows 7 Task Bar and quickly start/stop SharePoint.  Here's an example of what the shortcut path looks like:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command "D:\SP2010Manager\StartStopSP2010.ps1 start"

You also have to run the script as administrator and enable PowerShell to run .ps1 files (look for set-executionpolicy).

Happy SharePoint-ing!

[Edit 1/13/2010]:  Updated the ps1 file to disable/enable w3svc and iisadmin to ensure IIS does not load after a reboot.

Posted by Jorman | 4 Comments
Attachment(s): StartStopSP2010.ps1

Exception on custom ASP.Net page hosting SharePoint FormField controls

Working with one of my customers and they were wanting to setup an email notification on a custom site that was originally based on a blog site.  The notification needed to be sent whenever someone created/modified a post or a comment associated with the post, and the alert needed to be based on the Categories associated with the Post.  For example, there is a category named "IT" and users wanted to be notified anytime someone creates or modifies a post that has the "IT" category associated with it.  We created a simple sequential workflow to handle the logic of sending out the notifications; however, the problem came in when we modified their custom new and edit pages.  These were ASP.Net pages leveraging the SharePoint Form Field controls.  These pages originally did not display the Category field as they were not being used.  Since they wanted multiple categories for each item, we setup the out of the box Category field on the Post list to be Multi-select and then included a MultipleLookupField control to the page, and hooked up the control in the code behind, everything's working great.

We move the code and pages to the test server and they work until we hookup the workflow.  Then we get this exception:

Exception type: System.InvalidCastException
Message: Unable to cast object of type 'Microsoft.SharePoint.SPFieldWorkflowStatus' to type 'Microsoft.SharePoint.SPFieldLookup'.

That's odd, the page isn't hooking anything up to WorkflowStatus, so we unhook the workflow and the page starts loading.  Add the workflow back and the page still works.  IISReset and the page is still loading.  Thinking everything is done, we call it a night. 

Next day, we find out the exception is back.  To see what's going on I get a copy of the pages and setup a repro.  Luckily the issue reproduced on my dev box, so I attached windbg.exe [from the Debugging Tools for Windows install] to the app pool and run the following command to break on managed exceptions:

sxe clr

I then reproduce the issue and windbg breaks on the managed exception.  I ran !clrstack to confirm it's the stack from the ASPX Error page.  This confirms that SharePoint is looping through the controls on the page and hooking up the SharePoint column to the field controls that are specified, but based on the exception, when it's trying to load our MultipleLookupField control, it's trying to hook up the Workflow Status instead of the Category column:

0:020> !clrstack
OS Thread Id: 0x1450 (20)
ESP       EIP    
03f5e8a8 7793fbae [HelperMethodFrame_1OBJ: 03f5e8a8]
03f5e908 0b6b3820 Microsoft.SharePoint.WebControls.LookupField.CreateChildControls()
03f5ea18 0b6b306e Microsoft.SharePoint.WebControls.MultipleLookupField.CreateChildControls()
03f5ea3c 6d4fd2f8 System.Web.UI.Control.EnsureChildControls()
03f5ea68 0b14bfb1 Microsoft.SharePoint.WebControls.BaseFieldControl.OnLoad(System.EventArgs)
03f5ecb0 6d4fc2a3 System.Web.UI.Control.LoadRecursive()
03f5ecc8 6d4fc2fe System.Web.UI.Control.LoadRecursive()
03f5ece0 6d4fc2fe System.Web.UI.Control.LoadRecursive()
03f5ecf8 6d4fc2fe System.Web.UI.Control.LoadRecursive()
03f5ed10 6d4fc2fe System.Web.UI.Control.LoadRecursive()
03f5ed28 6d4fc2fe System.Web.UI.Control.LoadRecursive()
03f5ed40 6d4fc2fe System.Web.UI.Control.LoadRecursive()
03f5ed58 6d4f8354 System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
03f5eeb0 6d4f7f84 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
03f5eee8 6d4f7eb1 System.Web.UI.Page.ProcessRequest()
03f5ef20 6d4f7e46 System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
03f5ef2c 6d4f7e22 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)

 

From here I took a look at the objects on the stack to try and figure out why SharePoint is linking the MultipleLookupColumn to Workflow Status.  You do this by running !dumpstackobjects [!dso] on the thread that has the exception on it.  This will dump out a list of objects associated with the stack:

0:020> !dso
OS Thread Id: 0x1450 (20)
ESP/REG  Object   Name
03f5e7d8 0251439c System.InvalidCastException
03f5e820 0251439c System.InvalidCastException
03f5e868 024a4dac Microsoft.SharePoint.WebControls.MultipleLookupField
---
03f5e948 024db9cc Microsoft.SharePoint.SPFieldCollection 
---

I can see the SPFieldCollection object which is the XML Schema of the fields associated with the list, so I dump this out using !dumpobject [!do]. 

0:020> !do 024db9cc
Name: Microsoft.SharePoint.SPFieldCollection
MethodTable: 03f0e3ac
EEClass: 077f49e0
Size: 84(0x54) bytes
GC Generation: 0
 (C:\Windows\assembly\GAC_MSIL\Microsoft.SharePoint\12.0.0.0__71e9bce111e9429c\Microsoft.SharePoint.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
07922af0  40010be        4 ...SPPersistedObject  0 instance 00000000 m_ContainingObject
71a72da0  400007a       10         System.Int32  1 instance        0 m_cDirtyUpdates
0969d670  400007b        8 ...rtyUpdateDelegate  0 instance 00000000 OnAddDelayDirtyUpdate
0969d754  400007c        c ...rtyUpdateDelegate  0 instance 00000000 OnRemoveDelayDirtyUpdate
71a72da0  400007d       14         System.Int32  1 instance       -1 m_potentialErrorIndex
096b832c  40027b4       18 ...SharePoint.SPList  0 instance 00000000 m_List
07b1dcd4  40027b5       1c ....SharePoint.SPWeb  0 instance 02451528 m_web
71a44620  40027b6       4c       System.Boolean  1 instance        1 m_bAll
71a72da0  40027b7       48         System.Int32  1 instance        6 m_iCount
00000000  40027b8       20                ARRAY  0 instance 024dba20 m_arrFieldSchema
71a44620  40027b9       4d       System.Boolean  1 instance        1 m_arrFieldSchemaInited
71a44324  40027ba       24      System.Object[]  0 instance 024dbce0 m_arrFieldSchemaFull
71a44620  40027bb       4e       System.Boolean  1 instance        1 m_arrFieldSchemaFullInited
71a70b54  40027bc       28        System.String  0 instance 024da8cc m_strExtendedFieldsXml
71a731a8  40027bd       2c ...ections.Hashtable  0 instance 024e2c38 m_htIdx2Fld
71a731a8  40027be       30 ...ections.Hashtable  0 instance 024e615c m_InternalNameDict
71a731a8  40027bf       34 ...ections.Hashtable  0 instance 024e626c m_InternalNameWithPrefixDict
71a731a8  40027c0       38 ...ections.Hashtable  0 instance 024ee088 m_DisplayNameDict
71a731a8  40027c1       3c ...ections.Hashtable  0 instance 00000000 m_IdDict
71a70b54  40027c2       40        System.String  0 instance 00000000 m_strScope
00000000  40027c3       44                       0 instance 00000000 m_Scopes
00000000  40027c4      b80                       0   shared   static s_SPFieldCreatorDelegates
    >> Domain:Value  01236970:NotInit  01739ae0:01b81e2c <<
71a6a35c  40027c5      b84 ....ReaderWriterLock  0   shared   static s_fieldMutex
    >> Domain:Value  01236970:NotInit  01739ae0:01b81e54 <<

From here, the  m_strExtendedFieldsXml property contains the data we're after, so we dump this out.  You could also dump out the other Schema properties to get one field at a time, but this property will dump out all the Schema as one string so it's easier to get:

0:020> !do 024da8cc
Name: System.String
MethodTable: 71a70b54
EEClass: 7182d65c
Size: 4352(0x1100) bytes
GC Generation: 0
 (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: <Fields><Field ID="{c042a256-787d-4a6f-8a8a-cf6ab767f12d}" ...<
Field></Fields> 
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
71a72da0  4000096        4         System.Int32  1 instance     2168 m_arrayLength
71a72da0  4000097        8         System.Int32  1 instance     2167 m_stringLength
71a71834  4000098        c          System.Char  1 instance       3c m_firstChar
71a70b54  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  01236970:01ac1198 01739ae0:01ac1198 <<
71a71784  400009a       14        System.Char[]  0   shared   static WhitespaceChars
    >> Domain:Value  01236970:01ac16f0 01739ae0:01ac6508 <<

The highlighed String property above contains all the XML schema that defines all the fields associated with the list.  I truncated the above view as it was 5-10 lines long on the page.  I then copied out the XML from the String property, create a new XML document in SharePoint Designer 2007 and paste in the info from the dump.  I can then right-click the page and select "Reformat XML" to clean up what I'm looking at.  I then search for the text "Category" and find the problem.  When the workflow was hooked up, the Workflow Status field has a StaticName and Name value of "Category".  I then find the field with a DisplayName of Category that we're trying to hook up to, and find it has a static name of "PostCategory".  

Workflow Status field that our control is picking up (some properties removed to keep the lines short):
 <Field DisplayName="CategoryAlert" Type="WorkflowStatus" ... StaticName="Category" Name="Category" ... >

Category field that we're trying to hookup (some properties removed to keep the lines short):
 <Field DisplayName="Category" Type="LookupMulti" ... StaticName="PostCategory" Name="PostCategory" ... />


SharePoint will pick up either the Display or Static name of a column, and in this case it was hitting the Workflow Status field first when enumerating controls and picking up the StaticName property before it was picking up the field with a DisplayName of Category.  We update our MultipleLookupField to point to the static name of our column to ensure there are no questions about which field we want, PostCategory, instead of the display name of Category and everything is happy.  Here's what the change looked like:

From:    <SharePoint:MultipleLookupField runat="server" ID="mlfCategory" ControlMode="New" FieldName="Category">

To:   <SharePoint:MultipleLookupField runat="server" ID="mlfCategory" ControlMode="New" FieldName="PostCategory">

You can get the Static and Display Name for fields from teh browser:

  1. Browse the list
  2. Click Settings, then List Settings
  3. Click a Column

The Display Name will show as the Column Name
The Static Name will display in the URL QueryString after Field =

Posted by Jorman | 0 Comments

Mystery of the SharePoint "White Screens"

Randomly users start receiving "white screen" responses when browsing a SharePoint site.  They essentially browse a URL and the response completes (IE shows Done in the status bar), but the response is an empty HTML page.  We found that this would only happen to a single server at a time and the symptom started after a process recycle.  Since the problem would continue until a 2nd process recycle, we grabbed a memory dump of the application pool in question.  Analysis of the memory dump indicated that there were HttpContext objects, but they were not running on threads yet they were not complete.  To get this data using Sos.dll, you find the HttpContext objects on the heap using !dumpheap -stat:

0:038> !dumpheap -stat
...
000007fef1c00bb0      105        35280 System.Web.HttpContext
...

The first value is the MethodTable for teh HttpContext object.  With this, you can run !dumpheap again and pass the MethodTable to  return all the HttpContext objects on the managed heap:

0:038> !dumpheap -mt 000007fef1c00bb0     
------------------------------
Heap 0
         Address               MT     Size
00000000ff3ae0f8 000007fef1c00bb0      336    
00000000ff3ba2b0 000007fef1c00bb0      336    
00000000ff3cfb70 000007fef1c00bb0      336    
00000000ff3e1f70 000007fef1c00bb0      336    
 

Now that you have an address for a specific HttpContext object, you can dump out the HttpContext and investigate the properties.  Let's dump out the first one in the list.  You can see that the errors property has a value set indicating that there is an exception associated with the request.  You can also see that the timeout value was set, however, the timeout has not been reached.  However, the Thread property is not set indicating that the request is not associated with a thread yet.  Other pieces that are odd is _configurationPath, _currentHandler, _handler, and _appInstance are all null.  [alot of the properties were removed to keep this as short as possible]

0:038> !do 00000000ff3ae0f8
Name: System.Web.HttpContext
MethodTable: 000007fef1c00bb0
EEClass: 000007fef1862378
Size: 336(0x150) bytes
 (C:\Windows\assembly\GAC_64\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
              MT Offset            Type   Value Name
000007fef1c036d8   8 ...IHttpAsyncHandler 0000000000000000 _asyncAppHandler
000007fef1c03208  10 ...b.HttpApplication 0000000000000000 _appInstance
000007fef1c03748  18 ....Web.IHttpHandler 0000000000000000 _handler
000007fef1c03b88  20 ...m.Web.HttpRequest 00000000ff3ae248 _request
000007fef1c03f08  28 ....Web.HttpResponse 00000000ff3ae398 _response
000007fef1c04420  30 ...HttpServerUtility 0000000000000000 _server
000007fef78fdef8  48 ...ections.Hashtable 000000019f753ae8 _items
000007fef78fd488  50 ...ections.ArrayList 000000019f753a80 _errors
000007fef7936ad8 138      System.DateTime 00000000ff3ae230 _timeoutStartTime
000007fef78f5770 11d       System.Boolean                1 _timeoutSet
000007fef79369d8 140      System.TimeSpan 00000000ff3ae238 _timeout
000007fef78f7040  a0 ....Threading.Thread 0000000000000000 _thread
000007fef78f5770 11e       System.Boolean                0 _isAppInitialized
000007fef1c03748  f0 ....Web.IHttpHandler 0000000000000000 _currentHandler

 

This was pretty odd, so we looked at the exception linked to the HTTPContext object.  You do this by running !do on the address next to _errors.

0:038> !do 000000019f753a80
Name: System.Collections.ArrayList
MethodTable: 000007fef78fd488
EEClass: 000007fef7501ea0
Size: 40(0x28) bytes
 (C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef78e4390  400094c        8      System.Object[]  0 instance 000000019f753aa8 _items
000007fef78fd810  400094d       18         System.Int32  1 instance                3 _size
000007fef78fd810  400094e       1c         System.Int32  1 instance                3 _version
000007fef78f5e90  400094f       10        System.Object  0 instance 0000000000000000 _syncRoot
000007fef78e4390  4000950      388      System.Object[]  0   shared           static emptyArray
                                 >> Domain:Value  000000000315f0d0:000000019f3a1230 00000000031d5540:000000019f3ac958 <<

Since _errors of an ArrayList, you have to dump out the _items collection to get to the actual exceptions:

0:038> !dumparray 000000019f753aa8
Name: System.Object[]
MethodTable: 000007fef78e4390
EEClass: 000007fef74feb18
Size: 64(0x40) bytes
Array: Rank 1, Number of elements 4, Type CLASS
Element Methodtable: 000007fef78f5e90
[0] 000000019f72e768
[1] 000000019f756400
[2] 000000019f75b928
[3] null


From here, you can run !PrintException on the first one.  You can continue running !PrintException on the InnerException, until you find the last InnerException.  In this case, they would all point to the following as the root exception:

0:038> !PrintException 000000019f72dd50
Exception object: 000000019f72dd50
Exception type: System.IO.FileLoadException
Message: Could not load file or assembly 'Microsoft.SharePoint.intl, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c' or one of its dependencies. Either a required impersonation level was not provided, or the provided impersonation level is invalid. (Exception from HRESULT: 0x80070542)
InnerException: <none>
StackTrace (generated):
    SP               IP               Function
    0000000005D1CE90 0000000000000001 mscorlib_ni!System.Reflection.Assembly._nLoad
    0000000005D1CE90 000007FEF77CAB91 mscorlib_ni!System.Reflection.Assembly.InternalLoad
    0000000005D1CF20 000007FEF77CAD57 mscorlib_ni!System.Reflection.Assembly.InternalLoad
    0000000005D1CF80 000007FEF7832704 mscorlib_ni!System.Reflection.Assembly.Load
    0000000005D1CFC0 000007FF00327FCF Microsoft_SharePoint!Microsoft.SharePoint.CoreResource..cctor

StackTraceString: <none>
HResult: 80070542

 

The message makes it look like the authentication isn't quite working or that impersonation is not configured.  If you run err.exe on the HResult, you get ERROR_BAD_IMPERSONATION_LEVEL.   Considering a process recycle clears out this error, those are both unlikely, but to make sure we captured some network traffic to confirm that Kerberos is working and authentication is working. 

We then setup a live debug session and !token was indicating that the context of the users browsing were Impersonation tokens.  We captured another memory dump while the process was in a bad state, ran IISReset and captured a memory dump of the good process for comparison.  This didn't really shed any light on this particular problem as it was confirming the HttpContext objects were in a bad state which we already know.  We were unable to reproduce the issue by using the requests in the IIS logs and the users listed in the log.  I then remembered that IIS logs data when the request is complete.  Since the requests showing in the memory dump were not complete, they would not be in the log file.

I then sorted the HttpContext objects by the Execution Time in Excel to see which requests came in first.  You can use a script from Tess Ferrandez to get this type of data.  This opened this problem wide open.  The first requests were always web service calls to Lists.asmx and were coming from Biztalk.  We then worked with the Biztalk team that owns the application making the requests and we were able to reproduce the problem.  We would recycle the application pool, then the Biztalk team would send the request and all requests after this would return blank HTML pages.  Since the Biztalk request was triggering this behavior and none of the other requests were causing this problem, we took a look at the config file for the Biztalk web service call.  They were configured to make the call via WCF and they had a client configuration file.  The behariors section of the file looked like:

    <behaviors>
      <endpointBehaviors>
        <behavior name="EndpointBehavior" />
      </endpointBehaviors>
    </behaviors>


Since the behaviors section is not specifying the clientCredentials, the allowedImpersonationLevel is set to Identification.  This means that the server can get the Identity of the user, but it is unable to impersonate the user.  This lines up with the exception message from the memory dumps.  We then changed the configuration to use Impersonation as the allowedImpersonationLevel, and confirmed the white screens no longer occurred.

      <behaviors>
           <endpointBehaviors>
              <behavior name="ImpersonationBehavior">
                 <clientCredentials>
                      <windows allowedImpersonationLevel="Impersonation" />
                 </clientCredentials>
              </behavior>
           </endpointBehaviors>
        </behaviors>

MSDN Information on the allowedImpersonationLevel setting:
http://msdn.microsoft.com/en-us/library/ms731356.aspx

My SharePoint Sites displays a link that you no longer have access to

Under My LInks, My SharePoint Sites, you see a link to a site, but when you click on the link, you are directed to the Access Denied page.  It's possible, you may be running into this scenario:

  1. Sub site is setup to inherit permissions from the parent
  2. Users were in the Members group of the parent site. 
  3. The Profile Synchronization job ran and added the link to the subsite in My SharePoint Sites.
  4. Break permissions inheritance on the sub site.  This copies all the permissions into the sub site
  5. Remove all the parent groups from the permissions on the sub site

Let's look at a quick example.  You have a site collection at http://server/sites/teamsite.  You then have a sub web at /sites/teamsite/project1 that inherits permissions from TeamSite.  The Profile Synchronization timer job runs at the top of the hour and the users in the TeamSite Members group now have a link under My SharePoint Sites for TeamSite and Project1.  Under Project1, you do the following:

  1. Click Site Actions | Site Settings
  2. Click People and Groups
  3. Click Site Permissions
  4. Click the Actions menu, then Edit Permissions
  5. Click OK on the dialog.  At this point, Project1 has a copy of the permissions from TeamSite.
  6. On the Permissions page, select all the groups, click the Actions menu, select Remove User Permissions.  This removes all the TeamSite groups and users from the permissions of the site.

At this point, Project1 no longer has the TeamSite SharePoint groups; however, Project1's Viewers, Members, and Owners groups still reference them.  When the Profile Synchronization timer job runs, it's unable to determine which users are Members of the sub web and doesn't make any changes.  This results in the Project1 link showing up in your My SharePoint Sites menu, but you don't actually have access to the site.  Another symptom of this problem is that when you display the Open or Save As dialog in an Office application, you get prompted for authentication daily.  This is a result of the Office client synchronizing the My SharePoint Sites information.  See this post for more info on how that works.  There is also a fix in the Office 2007 Client Hotfix Update from June that avoid the Office clients from prompting for authentication.  KB Article 970950.

To fix this issue, you have to assign a SharePoint Group as the Members group on the sub web and all nested subwebs.  This allows the Profile Synchronization job to determine which users are Members and clean up the links on the server-side.  Once the link is cleaned up on the server-side, the Office client will sync up the next day and remove the link as well.  In my testing, if you do not have the above Office hotfix, you will get one last prompt on the client before the link is cleaned up.

  1. Browse the subsite
  2. Click Site Actions | Site Settings
  3. Click People and Groups
  4. Settings menu | Set Up Groups
  5. Create 3 new groups for this subsite associated with Viewers, Members, and Owners
  6. Repeat for the other sub webs under this particular sub web.

Overview of My SharePoint Sites and how the data is populated

SharePoint gives you the ability to get a list of sites that your login account is explicitly a member of.  This grouping of links is built on the server-side by the Profile Synchronization timer job that runs for each Web Application.  The data is stored in the SSP database and is visible in the following locations: 

SharePoint Web UI

  • Under your My Site by clicking the Memberships link

Memberships in My Site

 

  • From the My Links menu next to the "Welcome LoginName" link.  Once you have informaiton in the Memberships view of the My Site, there will be a menu item added named My SharePoint Sites.  Hovering over this menu item will show you all the links from your My Site, Memberships view.

My SharePoint Sites Menu in Browser

 

Client machines

  • These links are available on client machines in the Office Open/Save As dialog box as well.

My SharePoint Site in Office Dialog

 

You can run into various issues in which these links are not populated, or once populated are no longer cleaned up.  I wanted to provide some information on how SharePoint determines which links to add to the Memberships view as well as provide some information about how the Office client knows which links to show.  This should give some ideas for isolating and troubleshooting these issues.  For this example, we'll setup a Site Collection at http://companyportal/sites/Home.

  • When you create the Site Collection, there are three SharePoint Groups created.  Home Viewers, Home Members, and Home Owners.  These three SharePoint Groups are designated as the Viewers, Members, and Owners groups for the site.  You can validate that these groups are setup and have permissions to the site by following these steps:

    1. Browse the Site collection
    2. Click Site Actions | Site Settings
    3. People and Groups
    4. Settings menu | Set Up Groups.   You'll see which SharePoint Groups are designated as Visitors, Members, and Owner

      I'll be posting another blog on a scenario in which these groups can cause links to display in My SharePoint Links when you no longer have access to the site.

  • The Profile Synchronization timer job runs every hour and reads users that are explicitly added to the SharePoint Group listed as the Members group.  In this case, users that are explicitly added to Home Members.  A link to the Home site is added to the Memberships list in the My Site.  The user's login account has to be explicitly added to the Members group as the job does not expand domain groups.  It's also important to note that the user has to be in the Members group...being in the Owners group will not add the link.

  • At this point the My SharePoint Sites menu item will show up under the My Links menu.

  • When you open an Office client that is SharePoint aware (2003 and 2007), and you have configured a My Site on the client, a call is made to the GetLinks method of the following web service: /personal/<user>/_vti_bin/publishlinksservice.asmx

    • This call by default occurs once a day. 
    • The response of the web service call is the items from your My Site's Memberships data.
    • The timestamp of the sync is stored in the following registry key.  If you delete this registry key, the Office client will sync with the server the next time the Open/Save As Dialog is displayed.

      • HKEY_Current_User\Software\Microsoft\Office\12.0\Common\Portal\LinkPublishingTimestamp

  • The Membership data returned is used to create a Network Places under your local profile at:

    • DRIVE:\Documents and Settings\<LoginName>\Local Settings\Application Data\Microsoft\OFFICE\My SharePoint Sites

  • Office also creates a registry key for each link that stores the meta data that came down from the web service call at:

    • HKEY_Current_User\Software\Microsoft\Office\12.0\Common\Server Links

  • The information is then displayed in the Open/Save As dialog under My SharePoint Sites

To reset everyone's Memberships links on the server side:  [Word of caution...if the sync is failing for whatever reason, the links will remain empty until that is fixed...in other words, don't randomly run these steps.]

  1. Run:  Stsadm.exe -o Sync -DeleteOldDatabases 0
  2. All the Memberships information for all users should be cleared out.
  3. Wait for the Profile Synchronization Timer Job to fire.

To reset your My SharePoint Links on the client side:  [Same warning as above...if the web service call to the My Site fails, these links will not be repopulated until that connection is established.]

  1. Clear out the Network Places and Shortcuts under:


    DRIVE:\Documents and Settings\<LoginName>\Local Settings\Application Data\Microsoft\OFFICE\My SharePoint Sites

  2. Delete the following registry keys:


    HKEY_Current_User\Software\Microsoft\Office\12.0\Common\Server Links
    HKEY_Current_User\Software\Microsoft\Office\12.0\Common\Portal\LinkPublishingTimestamp

  3. Open an Office client, and open the Open File Dialog

  4. Click My SharePoint Sites and the links should repopulate.  May take 30-60 seconds since the web service call has to complete and the links have to be generated.

SharePoint My Site link stops redirecting users to their Personal Site

Hopefully this can save someone some time.  The symptoms are:

  • Clicking the My Site link in a SharePoint site results in an IIS 404 File Not Found error page being returned.  However, you are able to browse directly to the users My Site by browsing http://mysite/personal/user.  [http://mysite is the URL to the web application hosting the Personal Sites]. 
  • Browsing anything in _layouts on this Web Application returns an IIS 404 File Not Found error page.  For example, the following URL would fail:  http://mysite/personal/user/_layouts/settings.aspx.
  • IIS shows the _layouts directory and it is configured the way other working web applications are configured.

After doing some troubleshooting, we found that IIS is not trying to load any files from the Web Application content path.  So the web.config is not even being looked for when browsing _layouts or the root of the My Site.  We were able to find that the site collection in the root of the Web Application was missing.  You can check for a Site Collection at "/" using "stsadm.exe -o enumsites -url http://mysite" or browsing Central Admin | Application Management | Site Collection List.

The root site collection in the My Site Web Application redirects users to their Personal Site.  If the user does not have a My Site, this Site Collection creates one for them.  It also displays the public information about users.   Since this site was missing, SharePoint returns a 404 File Not Found message and no one is redirected to their My Site.

The reason the Site Collection was missing was because an admin tried to delete a user's My Site.  The tricky part in this situation is when a user browses another person's My Site, they get redirected to a page in the root site collection.  Here's the scenario:

  1. You need to delete user bobsmith's My Site and decide to do this by browsing the Site Collection, going into Site Settings and deleting the Site Collection.
  2. Open a browser and enter the URL to the persons My Site: http://mysites/personal/bobsmith . 
  3. Since you are not logged in as bobsmith, you get redirected to http://mysite/person.aspx?accountname...
  4. When you click on Site Actions | Site Settings, you are really doing this on the root Site Collection at "/" and not the one for bobsmith at /personal/bobsmith.  Deleting the Site Collection in this manner, will result in the root Site Collection being deleted.
  5. It is important to note that by default the SharePoint System account is the only account that can perform the delete action.

In order to get the Site Collection back, you need to do the following:

  1. Browse Central Administration | Application Management
  2. Click Create Site Collection
  3. Select the My Site Web Application
  4. Set the Title to My Site
  5. For URL, select the "/" option for the root 
  6. Select the My Site Host site template on the Enterprise tab
  7. Set the SharePoint System account as the owner.
  8. Click OK

 If you need to delete an individual's My Site, you need to use the Delete Site Collection option in Central Admin | Application Management, or the STSAdm.exe command-line (stsadm -o deletesite -url http://mysite/personal/bobsmith).

Posted by Jorman | 2 Comments
Filed under:

Check if a Word doc is password protected prior to uploading it to a web site

I apologize for the delay between blogs.  Switched jobs within Microsoft last December and moved from Texas to Southern California as part of that transfer.  Needless to say, I've been insanely busy :-)

Anyway...this was an interesting question I received a couple months back and have been meaning to blog about it.  The problem:  How can you check if a Word document is password protected when uploading it to an ASP.Net based application? 

This may not seem that complicated...you could upload the file to the server, then load the Word Object Model on the server and check some properties.  Not so fast there!  The problem with this approach is that leveraging the Office Object Model in a service based process [i.e. non-interactive] is not supported.  Someone wrote a Knowledge Base article on this, and if you call in to support for help with a hanging ASP.Net application and you are automating Office, this will likely be the link you will receive in the email explaining what's going on: 

257757  Considerations for server-side Automation of Office
http://support.microsoft.com/default.aspx?scid=kb;EN-US;257757

Without being able to load the Office DLL's into the ASP.Net application pool, this leaves you with running some code on the client to perform the check.  I came up with a sample that leverages JavaScript, a .Net Winform control running in the browser, and a File Upload control to do this work.  The idea was to only leverage the .Net Winform control to check if the file was password protected.  The sample also shows how to handle events across the Winform control and JavaScript. 

The general process is:

  1. User types in or selects a file by clicking the Browse button generated by the File Upload HTML control
  2. When the focus moves away from this control, JavaScript runs that sets a public property on the WinForm control to the FileName the person is uploading.
  3. When the user clicks Submit, which is the WinForm control, code runs to open the Word Document to determine that the file is password protected.  If the file is not password protected, the OnSubmitClick event is fired.
  4. There is JavaScript in the ASPX page that listens for the OnSubmitClick event and submits the form when this fires.  This posts the file to the server.

The code is a sample, there could be more checks to ensure that Word is installed on the box and ensuring that they are selecting a supported file extension, but the idea is the same.  The Submit button or file upload control could use some look-and-feel changes to make them look the same.  There are two projects in the attached Zip file:

  • Uploader – this is the WinForm project.  The .cs file is commented with the details.
  • UploaderTest – this is an ASP.Net project.  Both the ASPX and the .cs file are fully commented.

The only downside to having to run the .Net code on the client is client-side permissions.  By default, code running in the browser runs in one of 3 Code Access Security (CAS) groups that have varying levels of restriction (LocalIntranet, TrustedSites, and Internet).  None of which give access that you need to do what you want.  If the URL does not contain periods [typically only in an Intranet environment], give the LocalIntranet zone FullTrust to have your code run or you can create a custom security code group to fully trust your specific URL.  If the URL has periods, it will run in the InternetZone by default.  Here are the steps in case you are unfamiliar:

1.       On the client machine
2.       Open Administrative Tools
3.       Open the latest .Net Configuration (1.1 or 2.0 Framework)
4.       Expand My Computer | Runtime Security Policy | Machine | Code Groups
5.       Right-click All_Code, select New…
6.       Fill in the Name and click Next
7.       From the “Condition type” dropdown, select URL
8.       For the URL, type in the path to the directory hosting the ASPX and DLL:  http://web01/myapp/*
9.       Click Next and use Full Trust and finish the wizard

Good luck!

Posted by Jorman | 1 Comments

Attachment(s): Sample.zip

Loading C++ Assemblies in ASP.Net

When you reference a Native C++ assembly from ASP.Net you may run into the following error:

System.IO.FileNotFoundException: The specified module could not be found.
(Exception from HRESULT: 0x8007007E)

[FileNotFoundException: The specified module could not be found. (Exception from HRESULT: 0x8007007E)]
System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) +0
System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +211
System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +141
System.Reflection.Assembly.Load(String assemblyString) +25
System.Web.Configuration.CompilationSection.LoadAssemblyHelper(String assemblyName, Boolean starDirective) +32

[ConfigurationErrorsException: The specified module could not be found. (Exception from HRESULT: 0x8007007E)]
System.Web.Configuration.CompilationSection.LoadAssemblyHelper(String assemblyName, Boolean starDirective) +596
System.Web.Configuration.CompilationSection.LoadAllAssembliesFromAppDomainBinDirectory() +3591161
System.Web.Configuration.CompilationSection.LoadAssembly(AssemblyInfo ai) +46
System.Web.Compilation.BuildManager.GetReferencedAssemblies(CompilationSection compConfig) +177
System.Web.Compilation.BuildProvidersCompiler..ctor(VirtualPath configPath, Boolean supportLocalization, String outputAssemblyName) +180
System.Web.Compilation.ApplicationBuildProvider.GetGlobalAsaxBuildResult(Boolean isPrecompiledApp) +3558605
System.Web.Compilation.BuildManager.CompileGlobalAsax() +51
System.Web.Compilation.BuildManager.EnsureTopLevelFilesCompiled() +462

[HttpException (0x80004005): The specified module could not be found. (Exception from HRESULT: 0x8007007E)]
System.Web.Compilation.BuildManager.ReportTopLevelCompilationException() +57
System.Web.Compilation.BuildManager.EnsureTopLevelFilesCompiled() +612
System.Web.Hosting.HostingEnvironment.Initialize(ApplicationManager appManager, IApplicationHost appHost, IConfigMapPathFactory configMapPathFactory, HostingEnvironmentParameters hostingParameters) +642

[HttpException (0x80004005): The specified module could not be found. (Exception from HRESULT: 0x8007007E)]
System.Web.HttpRuntime.FirstRequestInit(HttpContext context) +3539851
System.Web.HttpRuntime.EnsureFirstRequestInit(HttpContext context) +69
System.Web.HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr) +252

 

The core cause to this problem is in the way the operating system loads native DLL's at runtime. Native DLL's are loaded using the following logic which does not include the Temporary ASP.net Files nor the applications /bin folder. This problem will also occur in any .Net application if the Native DLL is not included in the /bin folder with the .EXE file or if the DLL is not in the Path Environment Variable.
 

  1. The directory from which the application loaded.  In the case of ASP.Net, this will resolve to %windir%\Microsoft.Net\Framework\v###\ or %windir%\system32\inetsrv for IIS 6.  
  2. The current directory.  In the case of ASP.Net, this will resolve to %windir%\System32\inetsrv for IIS 6.  If using the built-in web server, this resolves to a path under C:\Program Files\Microsoft Visual Studio 8.
  3. The Windows system directory.  Use the GetSystemDirectory function to get the path of this directory. 
  4. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory. 
  5. The directories that are listed in the PATH environment variable.


============
The options:
============

  1. Use DLLImport to load the dll using a relative or absolute path at runtime.
  2. Set the PATH Environment Variable so the ASP.Net process can locate the C++ DLL.  You can set this property at runtime so that it only affects the process running your code.  You can also set this globally in the System Properties (Environment Variables | PATH property). Setting this programmatically does not require a reboot and you can point the PATH to the /bin folder of the web app if you want to be able to do XCopy deployments of your ASP.Net application.  Here are the steps to set the Path programmatically from ASP.Net.

 Option 2.a - If you want your Native C++ DLL’s loaded from the /bin of the ASP.Net application. 

  1. Native C++ DLL project
    1. Use al.exe to build a NativeWrapper for the Native C++ DLL.  This allows you to bring the Native C++ DLL along with the DLL that is referencing it.  You can add this in the Post Build Script of the Native C++ DLL Project to automate this.

      al.exe /link:"$(TargetPath)" /out:"$(TargetDir)$(TargetName).NW.dll" /platform:x86
    2. Managed C++ DLL Project
      1. Reference the NativeWrapper DLL - when you build this project, the native wrapper and Native C++ DLL files are copied to the output directory along with the managed C++ DLL
      2. Set Delay Load DLLs to the Native DLL.  (C++ Project Properties, Expand Linker, select Input, Delay Loaded DLLs) - This prevents the Native DLL from loading when the process starts, giving you a chance to set the PATH Environment variable.  If you don’t set this property, moci.net and its dependencies (i.e. the Native DLL) will be loaded before any of your ASP.Net code can run and this will fail unless you have set the PATH environment variable on the machine level.
    3. ASP.Net Project
      1. Reference the managed C++ DLL - The managed C++ DLL, Native C++ DLL, and NativeWrapper DLL are moved into the applications /bin folder.
      2. Add a Global.asax with the following code.  (Right-click the Web Application, select Add, New Item, select Global Application Class).  You could also use an HTTPModule compiled into a DLL if you don’t want people to be able to change your code.  Application_Start runs one time when the application loads and this code will set the PATH environment variable for the process to include the /bin directory of the application.

        protected void Application_Start(object sender, EventArgs e){
            String _path = String.Concat(System.Environment.GetEnvironmentVariable("PATH"), ";", System.AppDomain.CurrentDomain.RelativeSearchPath);
            System.Environment.SetEnvironmentVariable("PATH", _path, EnvironmentVariableTarget.Process);
        }

 Option 2.b - If you want your Native C++ DLLs to load from an installation path outside of the web site you can avoid the AL command.  You would still need to set the Delay Load property on any Managed C++ DLL that loads the Native C++ DLL’s as well as set the Environment Variable.  If you choose to go this route, you can load the path to your Native C++ DLL’s dynamically from the web.config file at runtime:

  1. Native C++ DLL Project - don’t need to do anything in the Post Build Script
  2. Managed C++ DLL Project
    1. Configure Delay Load DLL’s and specify the Native C++ DLL
  3. ASP.Net Project
    1. Reference the Managed C++ DLL - only Managed C++ DLL is in the /bin
    2. In web.config, add the following…use a path where the Native C++ DLL is located:
         <appSettings>
          <add key="NativePath" value="C:\MyNativeDLLs"/>
         </appSettings>
    3. In global.asax, add the following:

      protected void Application_Start(object sender, EventArgs e){
          String _path = String.Concat(System.Environment.GetEnvironmentVariable("PATH"), ";", ConfigurationSettings.AppSettings["NativePath"]);
          System.Environment.SetEnvironmentVariable("PATH", _path, EnvironmentVariableTarget.Process);
      }
Posted by Jorman | 7 Comments

Referencing .js files for the AJAX Control Toolkit

I was recently working with someone that was leveraging the AJAX Extension and the Control Toolkit and they were running into an issue where a customer's environment was causing issues making requests to ScriptResource.axd.  Since they couldn't change the environment, they needed to reference the .js files directly.  Here's a rundown of the steps in case anyone needs to do this in the future:

1. Create a folder to hold the .js files in your site.  In my case I created a "Scripts" folder on the root of my app.

2. Create the following folder structure under the folder from Step 1.  The folder that is a version number should match the version of the control toolkit.  This can be found on the Releases page for the extension.

        - <Root>
                - Scripts
                        - AjaxControlToolkit
                                - 1.0.10301.0

3. Open the Control Toolkit project and build the solution.  This should generate the \obj folder that contains all the .js files.

4. Copy the Script files for the toolkit project to the site

        From:  <path_to_unzipped_toolkit>\AjaxControlToolkit\obj\Debug

        To:    1.0.10301.0 folder from above

5. Copy the Script files for the AJAX Extension:

        From:  Copy the System.Web.Extensions folder and all it's contents from C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\v1.0.61025\MicrosoftAjaxLibrary

        To:    ~/Script folder.  You should have a folder structure similar to:

        - <Root>
                - Scripts
                        - System.Web.Extensions
                                - 1.0.61025.0

6. Set the ScriptPath on the <scriptManager> to "~/Scripts"

7. Make sure the controls are not explicitly setting ScriptPath as that will override the ScriptManager property.

Now when you browse the page, the .js files will be loaded from the ~/Scripts folder instead of via calls to ScriptResource.axd.  If you're getting script errors, take a netmon and ensure all the requests to the .js files are returning a status code of 200 or 304.

Posted by Jorman | 1 Comments
Filed under:

JavaScript errors occur...only when the script is in a .js file

This was tricky to track down and hopefully the troubleshooting information will help you should you run into this type of error and can't track it down. 

Symptom
When browsing a web application you receive the following JavaScript errors.

JavaScript errors:

Error 1:
Syntax Error

 Error 2:
 Object expected

If using 3rd party ASP.Net controls, you may see errors similar to the following:

ASP.Net Error related to 3rd party control
Unable to find script library '/aspnet_client/basicframe_webcontrols_basicdatepicker/1_1_1959/bdplite.js'. Try placing this file manually by uploading the /aspnet_client directory to your web application root.

Typically the aspnet_client directory can be found at the following path:
C:\inetpub\wwwroot\aspnet_client


Looking at the IIS logs will show that requests to these pages are serving correctly with a 304 or 200 response. Investigation of the .js file in the IE cache or network trace response will show you that there is additional text at the end of the .js files. For example, you have a .js file with the following:

function AlertMe()
{
  alert('The JS File loaded');
}

However, in the netmon or IE cache, you see the following:

function AlertMe()
{
  alert('The JS File loaded');
}<div>Confidential Information</div>

The extra information results in Internet Explorer throwing the JavaScript errors. BTW, the error doesn't occur in Firefox as it seems to be more tolerant of the invalid script.

Troubleshooting Steps
Check the IE cache to see if there is extra data appended to the .js file.

  1. Tools | Internet Options in IE
  2. General Tab | Settings button under Browsing History
  3. Click View Files
  4. Sort by Internet Address and find the URL to your site
  5. Do you see the .js file being requested? If so, open it in notepad and look for extra text at the end or any characters not in the original file on the server.

You can also use a tool that captures the network data (Netmon 3, Ethereal, Fiddler, Web Development Helper, HttpWatch, Firebug, etc).  However, if IE is caching the page, you will see a request with the If-Modified-Since header coming from the client and the server will return a response with a 304 status code which will not contain the script.  The If-Modified-Since header indicates the browser has the file in cache, and is asking the server if it has a newer copy.  In order to avoid this behavior, you can force IE to always request the actual page instead of using cache.  This way you can see the 200 response coming from the server with the contents of the .js file to see if anything extra is added to your script.

  1. Open IE
  2. From Tools menu, select Internet Options
  3. Click Settings under Browsing History (IE7)
  4. Click Every Time I Visit the Web Page, click OK
  5. Use network capture tool and browse the page.

Cause
In this particular scenario, the "Enable document footer" option in IIS was enabled on the folders serving the .js files. This option appends text to the end of any static documents served:

  1. Open IIS Manager
  2. Right-click the folder hosting the test pages, select Properties 
  3. Click the Documents tab 
  4. Uncheck "Enable document footer". 

Anything in the pipeline could potentially cause this.  Isapi filters, proxy servers, HttpModules/handlers(if .js is mapped to aspnet_isapi.dll), etc.

Posted by Jorman | 1 Comments

Fix: Random FileNotFoundException in ASP.Net 2.0 application

Issue  -  Random FileNotFoundException when browsing ASP.Net 2.0 application.  An event similar to the following will be logged in the event viewer.  The file name in the exception is typically different every time:

Exception message: Could not load file or assembly 'App_Web_-a8debde, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

  Stack trace:    at ASP.UserControl_ascx.__BuildControlmain()
   at ASP.UserControl_ascx.__BuildControlTree(dynamic_default_ascx __ctrl)
   at ASP.UserControl_ascx.FrameworkInitialize()
   at System.Web.UI.UserControl.InitializeAsUserControlInternal()
   at System.Web.UI.UserControl.InitializeAsUserControl(Page page)
   at ASP.default_aspx.__BuildControlhtml_content()
   at ASP.default_aspx.__BuildControlTree(default_aspx __ctrl)
   at ASP.default_aspx.FrameworkInitialize()
   at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   at System.Web.UI.Page.ProcessRequest()
   at System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context)
   at System.Web.UI.Page.ProcessRequest(HttpContext context)
   at ASP.default_aspx.ProcessRequest(HttpContext context)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Workarounds  -  In some cases, it's necessary to clear the Temporary ASP.Net Files folder and restart the application pool.  We've found in some cases that setting batch="false" in the web.config file will resolve the issue as well, however, there are some perf reasons for not doing that long term in production.

Getting the Fix  -  The KB for this fix is not yet public (I'll link it as soon as it is), however, the fix is available.  You will need to contact Microsoft Support (http://support.microsoft.com/) and ask for the fix from KB article 934839.

Posted by Jorman | 4 Comments
Filed under:

Getting real-time updates from remote process using the AJAX Extension

This sample was a result of an idea from Kent Post at Akamai.  He was wanting to be able to launch a process on a remote server and get real-time updates in the browser.  In this case, we focused on running WinPE scripts to build ISO's on the server.  The WinPE scripts can take a long time to process, and the goal is to launch the process and see what was going on without having to be logged into the server console using the browser.  The attached sample provides a way to perform these actions by using System.Diagnostics to launch the process and the AJAX Extension to get updates.  Before getting into the sample, let's look at some of the complications of doing this type of work.

  1. Permissions on the server - By default, the application pool running ASP.Net cannot run other processes on the server.  The process by default runs as Network Service which typically won't have Read access to the .bat, .cmd, .exe you're trying to run.  If you're writing files, it won't have access to write to directories either.
  2. Getting the data back to the client - Once you launch the process, how do you get the data down to a remote browser?  HTTP is a disconnected protocol, so once you spin up the process, you need some way of making additional requests and getting the output. 
  3. Application can not be interactive - Making sure the application you're running does not have an interface that requires interaction.  Keep in mind you are launching a child process from a non-interactive service and there is no way for a remote or local user to actually interact with the program.  Batch files are good for this scenario.

The first obstacle is relatively easy to overcome.  For the demo, you can use a file based web site in Visual Studio 2005 and the built in web server.  In this scenario, the web server and code run as the account you are logged into the box with and assuming this account has appropriate permissions to launch the program and write to any area that the program writes to, the app will work fine.  From a server perspective, we ran the Application Pool as Local System.  You can setup IIS with a new application pool running under local system and assign the application pool to the specific application to ensure other code is not running with elevated privileges.  Another option is to use PInvoke and call CreateProcessAsUser and explicitly set desktop permissions for the application pool account you're using.  This is a bit more complicated and won't be covered here.  If you're interested in looking into that approach for launching processes, check out Q165194 

165194 - INFO: CreateProcessAsUser() Windowstations and Desktops
http://support.microsoft.com/support/kb/articles/Q165/1/94.asp

The second problem of getting the output has a couple of options. One way to go is to pipe the output from the program to a text file and then redirect the browser to the textfile.  If you haven't done this before, the following pipes the output of the dir command to the file c:\output.log:  dir >c:\ouptut.log  This gets you the output after the application has completed execution.  However, in order to get the data as it's occurring, you have to make multiple requests to the server and access the StandardOutput stream directly.  In this sample, we use the AJAX Extension, the setInterval method in JavaScript, the System.Diagnostics.Process class, and a custom class that's cached on the server to handle this scenario.

Overview of the sample 
The attached files provide all the source to run the sample.  Here's a rundown of what the app does and what is in the .zip files.

  1. User browses Default.aspx - If the user does not have a cookie called MyGUID, one is created for them.  The cookie value is used to uniquely identify the requests when making web service calls and for caching.
  2. User clicks the Launch button
    1. JavaScript posts to the LaunchProcess method of the ProcessLauncher.asmx web service using the AJAX Extension  
    2. LaunchProcess method does the following:
      1. Creates an instance of the ProgramWatcher class
      2. Calls the Execute method of ProgramWatcher
        1. Execute method launches a ThreadPool thread
        2. Runs the ReadLog.exe program
        3. Redirects the StandardOutput of the ReadLog.exe program to a Stream property of the ProgramWatcher instance
      3. The ProgramWatcher instance is cached using the MyGUID cookie value from Step 1
  3. When the Async call returns, a timer kicks off that runs the GetStatus JavaScript method after 750 milliseconds.
  4. GetStatus JavaScript method runs
    1. Timer is stopped to prevent async callbacks from getting out of sync.  i.e. IE can make 2 calls per server, this prevents 2 requests leaving the client and the 2nd request returning before the first request and messing up the output.
    2. GetStatus method of the ProcessLauncher webservice is called
      1. GetStatus loads the ProgramWatcher method out of cache using the MyGUID cookie value
      2. Calls the ReadOutput method of the ProgramWatcher instance passing it how many bytes to read from the Stream property on ProgramWatcher - in this sample 50 bytes at a time.
      3. Returns the bytes returned from ReadOutput
  5. Client parses the response and handles output that contains \r vs. \r\n.  See the Issues section below for why this is important.
  6.  Timer started again and Steps 4 - 5 occur again until the GetStatus method returns Null

Included Files
Here's a list of the files included in the .zip files with a summary of what they do.  The code is commented thoroughly.

  •  AjaxBatchProcess.zip
    • App_Code
      • ProgramWatcher.cs - This class is what drives the process on the server side.  It contains an Execute method that launches a program via System.Diagnostics.Process and redirects the StandardOutput to a property on the class.  The class is cached so that the browser can get the output from the process in chunks.
      • ProcessLauncher.cs - Web Service methods that allow the AJAX Extension to generate JavaScript proxy classes that are used to launch the process on the web server and also get the output in chunks.
    • AjaxBatchProcess.js - JavaScript file that calls the webservice methods and also handles getting and parsing the output.
    • Default.aspx - Page requested by an end user.  Has a ScriptManager that hooks up AjaxBatchProcess.js and ProcessLauncher.asmx
    • ProcessLauncher.asmx - Web Service requested by the client.
    • Web.config - Web.config from the AJAX Extension project
  • ReadLog.zip
    • ReadLog.exe - Command-line program that reads the build_pe_out.txt file and pushes the output to the console.
    • build_pe_out.txt - Output from WinPE commands.
  • ReadLog-Source.zip
    • Program.cs - source for the ReadLog program.  This program reads in a file passed in as an argument and pushes the text to the console.
    • Default C# console project files

Here are the steps to use the files attached to the blog:

  1. Unzip AjaxBatchProcess.zip to a folder in your file system.
  2. Unzip ReadLog.zip to c:\ReadLog
  3. Open Visual Studio 2005
  4. From the File menu | Select Open Web Site
  5. Select the path from Step 1 (the folder that contains app_code, default.aspx, etc. from the .zip file)
  6. Right-click Default.aspx, select View in Browser
  7. Click the Launch button

Issues
I wanted to highlight a few areas and why we chose to go that route.

Caching the ProgramWatcher and the Stream property

This is an important part of the application and is the piece that allows access to the output from the application in real-time.  In this case you have a program that is redirecting the StandardOutput to a Stream.  This is essentially writing bytes into memory with a pointer at the end. 

These are bytes<StandardOutput Pointer>

The ProgramWatcher class sets the StandardOutput stream to a property called Stream.  This gives you another pointer to the same bytes that starts at the beginning. 

<ProgramWatcher.Stream pointer>These are bytes<StandardOutput Pointer> 

With this setup, you have the System.Diagnostics.Process adding bytes to the end of the stream via StandardOutput and ProgramWatcher.Stream reading the same bytes with a pointer that starts at the beginning.  The ProgramWatcher instance is cached so that when you Read bytes off of ProgramWatcher.Stream, you are incrementing the pointer. 

These are <ProgramWatcher.Stream pointer> bytes.  This is additional data <StandardOutput Pointer>

When the GetStatus method is called again, you are accessing the same instance of the class, so the pointer is where you left it.  I was making this way to hard, so thanks to Todd Carter for helping clear this up.

Parsing the \r and \r\n in the output returned by GetStatus

This is important as console applications handle text ending in \r different than \r\n.  In the case of WinPE, you get output showing the percentage complete.  The text looks like:

      |6.0.6001.16464 |  +  | WinPE-FontSupport-JA-JP-Package\r\n[==============             25.0%                          ]\r[===========================50.0%                          ]\r[============================75.0%===========               ]\r
[==========================100.0%==========================]\r\nMore Text

In the console, this renders as the following when it is complete:

      |6.0.6001.16464 |  +  | WinPE-FontSupport-JA-JP-Package
More Text

However, while the process is running, you would see the WinPE-FontSupport-JA-JP-Package line followed by the percentage.  When the next line ending in \r is written to the console, the previous line is replaced with the updated percentage.  The problem is that the browser doesn't handle \r and \r\n the same way.  So you would end up with the following:

       |6.0.6001.16464 |  +  | WinPE-FontSupport-JA-JP-Package
[==============             25.0%                          ]
[===========================50.0%                          ]
[============================75.0%===========               ]
[==========================100.0%==========================]
More Text

Not real pretty.  In the sample, I parse the output and if the output ends in \r, I put the information in the TextBox.  If the text ends in \r\n, I clear the textbox and add the line to the TextArea.  This gets pretty close to the same output you see in a console and avoids a lot of noise and clutter in the output.

Hopefully this sample and the information provided will help anyone out there trying to solve similar issues.

Posted by Jorman | 0 Comments
Filed under:

Attachment(s): FullSample.zip

Viewstate error in mobile ASP.NET app

This is my third mobile post this week!  This one was tricky to track down so hopefully this will save someone some time.  The symptom you see is that users get the following exception.  If you're using ASP.NET 2.0, we would log this to the Event Viewer by default thanks to Web Events:

Exception of type 'System.Web.HttpUnhandledException' was thrown. --->

System.Exception: The page requires session state that is no longer available. Either the session has expired, the client did not send a valid session cookie, or the session state history size is too small. Try increasing the history size or session expiry limit.

This error would appear randomly throughout the day in a high throughput site.  We were able to track this down using the IIS logs, Network Monitor, and in this case, the exceptions logged in the Event Viewer.  The event logging in 2.0 captures a lot of information that is useful for lining up the exception with data gathered.  In this instance, we used the client IP linked to the request and then filtered the Network Monitor logs using this IP.  This allowed me to track the user's requests to see how they were using the site at the time of the failure.

In this case, we could see the client making requests for a few minutes, then their traffic would go away for longer than the session timeout.  Their next request would come in for a page that they weren't browsing prior and would cause the exception.  Looking at this data, we determined that users were bookmarking the site on their mobile devices.  While this normally isn't a big deal, there are some devices that result in an __viewstate field in the QueryString.  When you bookmark this URL, the initial request is now going to cause the mobile page to lookup the viewstate which is stored in Session for mobile forms.  Since this is the first request, the session is just now being initialized, the ViewState isn't available, and the exception is thrown.

You can avoid this problem by adding the following code to Session_Start in the global.asax or a HttpModule.  You can even get fancier and add some code to only remove the __viewstate QueryString if your pages are looking for other items in the QueryString collection.  This code will ensure that when a new session is being created, that the __viewstate QueryString is not part of that request.

    void Session_Start(object sender, EventArgs e)
    {
        // Code that runs when a new session is started
        if (HttpContext.Current.Request.QueryString.Count > 0)           
              HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Url.LocalPath.ToString());
    }

You could also track this down by logging the QueryString in the IIS logs and looking for __viewstate in that field.  The exception logged in the Event Viewer in 2.0 also log the UTC time to make it easy to line up the IIS entries which use UTC by default with the exception.

Posted by Jorman | 0 Comments
Filed under:

Determining what capabilities ASP.NET is using to render pages

When you make a request to an ASP.NET based site, the browser's capabilities are determined by the <browserCaps> section in the 1.x Framework and the .browser files in the 2.0 framework.  Knowing which capabilites that ASP.NET is using to render the page can be especially important in mobile applications. 

You can use the following page to return all the MobileCapabilities for the device by browsing the page:

<%@ Page language="c#" AutoEventWireup="true" Inherits="System.Web.UI.MobileControls.MobilePage" %>
<%
@ Import namespace="System.Web.Mobile" %>
<%
@ Import namespace="System.Reflection" %>
<script runat="server">
    
protected void Page_Load(Object sender, EventArgs e)
     {
         
MobileCapabilities capabilities = (MobileCapabilities)Request.Browser;
          Type t = typeof(MobileCapabilities);
          PropertyInfo[] propertyInfos = t.GetProperties();
          foreach(PropertyInfo pi in propertyInfos)
          {
                    System.Web.UI.MobileControls.
Label lbl = new 
                    System.Web.UI.MobileControls.
Label();
                    lbl.Text = pi.Name +
" = " + capabilities[pi.Name];
                    Form1.Controls.Add(lbl);
          }
     }
</script>
<mobile:Form id=Form1 runat="server">
</mobile:Form>

The MobileCapabilities are determined by the UserAgent string that ASP.NET receives.  You can log the UserAgent string in the IIS logs or you can use a tool like Fiddler or Web Development Helper in IE to get this information.  Once you have the UserAgent string, you can use Fiddler to pass the same UserAgent string while browsing in IE.  This will give you a larger viewing area for looking at hte list of MobileCapabilities.

Posted by Jorman | 2 Comments
Filed under:
More Posts Next page »
 
Page view tracker