Shawn Cicoria - CedarLogic

Perspectives and Observations on Technology

Recent Posts

Sponsors

Tags

General





Community

Email Notifications

Blogs I Read

Archives

Other

Use OpenDNS

Deployment of Resource files (*.resx) to App_GlobalResources under SharePoint

This is a continuation from

Deployment of Theme and Resource files

Resource File Deployment (Resx)

The second item was deploying Resource files to the App_GlobalResource directory present as a subdirectory under each IIS site for each SharePoint Web Application zone. Remember that you can have multiple IIS Sites for each “Logical” SharePoint Web Application.

The other requirement we had was that each Web Application needed to have its own set of resource files and were to be deployed and scoped as a Web Application feature.

The way the Timer job for resource was written was it would take a list of full file paths along with the target Web Application.  So, it’s the responsibility of the Feature code when it’s calling the CreateJob method to get that list of files, populate the string[] of files, and also obtain the target Web Application instance on the call.  All of these things are done in the feature activation code – which, I’ve left out of this sample just for brevity.

The CreateJob method as mentioned is below:

/// <summary>
/// Submit a App_GlobalResources copy job providing a list of files with full path
/// </summary>
/// <param name="webApp"></param>
/// <param name="files"></param>
public static void CreateJob(SPWebApplication webApp, string[] files)
{
    try
    {
        // Create new job
        GlobalResourceDeployJob grdj = new GlobalResourceDeployJob(webApp, files);
        grdj.SubmitJobNow(JOBNAME);
    }
    catch (Exception ex)
    {
        LogHelper.Log("Failed to create job {0} - {1}", JOBNAME, ex.Message);
        throw;
    }
}

Pretty straight forward, and the constructor is as follows:

/// <summary>
/// Here as it's required by the platform
/// </summary>
public GlobalResourceDeployJob() : base() { }

/// <summary>
/// Primary constructor used
/// </summary>
/// <param name="webApp"></param>
/// <param name="files"></param>
public GlobalResourceDeployJob(SPWebApplication webApp, string[] files)
    : base(JOBNAME, webApp, null, SPJobLockType.None)
{
    _files = files;
}

Again, no big deal..

The Execute override does most of the work as follows:

/// <summary>
/// Main method to actualy do the copy of the files
/// </summary>
/// <param name="targetInstanceId"></param>
public override void Execute(Guid targetInstanceId)
{
    try
    {
        this.Log("starting job {0}", JOBNAME);
        SPWebApplication webApp = this.Parent as SPWebApplication;

        if (webApp == null)
        {
            this.Log("job {0} - invalide WebApp found (isNull)", JOBNAME);

        }
        foreach (SPUrlZone zone in webApp.IisSettings.Keys)
        {
            // The settings of the IIS application to update
            SPIisSettings oSettings = webApp.IisSettings[zone];

            // Determine the destination path
            string destPath = Path.Combine(oSettings.Path.ToString(), "App_GlobalResources");

            foreach (var filePath in _files)
            {
                string fileName = Path.GetFileName(filePath);
                if (File.Exists(filePath))
                    this.Log("File {0} exists - will be overwriting", fileName);

                if ( !File.Exists(filePath ))
                {
                    this.Log("File {0} to copy doesn't exist at the source - skipping", filePath);
                    continue;
                }

                string destination = Path.Combine(destPath, fileName);
                this.Log("Copying filePath {0} to  {1}", filePath, destination);
                try
                {
                    File.Copy(filePath, destination, true);
                }
                catch (Exception ex)
                {
                    this.Log("Err - couldn't copy file {0} with exception {1}", filePath, ex.Message);
                }
            }

            this.Log("job {0} - For WebApp {1}: zone path: {2}", JOBNAME, webApp.Name, oSettings.Path); 
        }

        this.Log("ending job {0}", JOBNAME);
    }
    catch (Exception ex)
    {
        this.Log("Failed Job {0}  : {1}", JOBNAME, ex.Message);
        throw;
    }
}

An finally, to make life a little easier, A couple of extension methods that i use:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint;

namespace SharePointJobs
{
    public static class TimerJobExtensions
    {
        public static void SubmitJobNow(this SPJobDefinition jobDef, string jobName)
        {
            if (jobDef.Farm.TimerService.Instances.Count < 1)
            {
                jobDef.Log("Could not find TimerService on this box");
                throw new SPException("Could not run job. Timer service not found.");
            }

            foreach (SPJobDefinition job in  jobDef.Service.JobDefinitions)
                if (job.Name == jobName)
                {
                    jobDef.Log("Found existing job {0}- deleteing", jobName);
                    job.Delete();
                    jobDef.Log("Done deleting existing job {0}", jobName);
                }

            jobDef.Log("Scheduling job {0}", jobName);
            jobDef.Schedule = new SPOneTimeSchedule(DateTime.Now);
            jobDef.Update();

        }

        public static void Log(this SPJobDefinition jobDef, string message)
        {
            LogHelper.Log(jobDef.Name + ":" + message);
        }

        public static void Log(this SPJobDefinition jobDef, string message, params object[] args)
        {
            LogHelper.Log(jobDef.Name + ":" + message, args);
        }
    }

    
}

Again, the source for the sample is below, it just contains the Timer Jobs, no feature code (it’s simple to wire it up) and a WinForm app for submitting the jobs.

SharePointTimerJobSlnV3.zip

Enjoy…

Again, thanks to the following:

http://solutionizing.net/2008/07/09/updating-spthemesxml-through-a-feature/

http://www.devexpertise.com/2009/02/11/installing-a-theme-as-a-sharepoint-feature/

http://alonsorobles.com/2008/06/26/packaging-branding-and-themes-for-deployment-in-sharepoint-environments/

Thanks to Joe McPeak, Ketan Shah for feedback, discussions…

Posted: 01-31-2010 10:44 AM by cicorias | with no comments
Filed under: ,
Deployment of Theme and Resource files via Feature / Timer Jobs

Recently, we had a deployment scenario where we needed to deploy a custom theme and some resource files (resx) to the Farm and the Web Application zones respectively.

Theme Deployment via Feature / Timer Job

For the first feature, deployment of a theme, we initially went down the path assuming that we could scope the feature at Farm, and SharePoint would call the FeatureInstalled method in our Feature Receiver. Unfortunately, this all worked in development on our single machine environments. When it came time to deploy to a multi-server SharePoint environment we found that the FeatureInstalled event NEVER fired on all machines; however, the FeatureDeactivating and FeatureUninstalling fired every time when we de-activated/retracted the solution respectively. Even the FeatureActivated didn’t fire on all machines.

While I’ve been told and seen many instances on blogs and internal messages that FeatureInstalled should be firing on all the machines, I just never saw it happen (I had added lots of logging and saw nothing in the ULS logs) – this was tried on a couple of Farms.

So, in comes the timer job. The more I picked apart how jobs are created in the SharePoint timer jobs, the more it was evident that was the only solution that would be reliable and this issue could be put to bed.

So, the feature definition is scoped to Farm, and pointing to a simple Feature Receiver that on Activation:

<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
         Id="604C0C13-D59B-4cd4-BCD2-A90F8AF2266C"
         SolutionId="db6b8735-a4a0-47dc-a012-64e85373f150"
         Title="Theme to deploy"
         Description="Provides deployment of Theme to the Farm"
         Hidden="FALSE"
         ActivateOnDefault="FALSE"
         Scope="Farm"
         ReceiverAssembly="..."
         ReceiverClass ="...."
         Version="1.0.0.0">

  <Properties>
    <Property Key="TemplateId" Value="THEMESAMPLE" />
    <Property Key="DisplayName" Value="Theme Sample"/>
    <Property Key="Description" Value="Theme Sample"/>
    <Property Key="Thumbnail" Value="images/thwheat.gif"/>
    <Property Key="Preview" Value="images/thwheat.gif"/>
  </Properties>
</Feature>

The FeatureActivated method just creates a custom type that contains the properties, then makes a call into the custom timer job’s static method to CreateJob, which is as follow:

/// <summary>
/// Call this to create the job - set the properties as required
/// If you want to delete (remove) the theme setting from the SPTHEMES.xml file pass in 
/// delete == true and the TemplateID
/// </summary>
/// <param name="jobProperties"></param>
public static void CreateJob(ThemeDeploymentJobProperties jobProperties)
{
    LogHelper.Log("creating job {0}", JOBNAME);
    SPWebService wsvc = SPFarm.Local.Services.GetValue<SPWebService>();

    foreach (SPServer server in SPFarm.Local.Servers)
    {
        switch (server.Role)
        {
            case SPServerRole.Application:
            case SPServerRole.SingleServer:
            case SPServerRole.WebFrontEnd:
                LogHelper.Log("submitting job {0} for server {1}", JOBNAME, server.Name);
                ThemeDeploymentJob job = new ThemeDeploymentJob(wsvc, jobProperties);
                job.SubmitJobNow(JOBNAME);
                break;
            default:
                break;
        }
    }
    LogHelper.Log("done creating job {0}", JOBNAME);
}

The key to the above is getting the Web Service (which is present on any machine that has the Web Front End role) and use that in the base type for SPJobDefinition’s constructor, as shown below.  Also, submitting the job for each server – this actually assumes that the service is present on all machines in the farm, and generally that’s what / how I configure a Farm and recommend that approach.

public ThemeDeploymentJob() : base() { }

public ThemeDeploymentJob(SPWebService webService, ThemeDeploymentJobProperties properties)
    : base(JOBNAME, webService, null, SPJobLockType.None)
{
    SetProperties(properties);
}

The rest of the code, and and much inspiration comes from the following samples, etc..

http://solutionizing.net/2008/07/09/updating-spthemesxml-through-a-feature/

http://www.devexpertise.com/2009/02/11/installing-a-theme-as-a-sharepoint-feature/

http://alonsorobles.com/2008/06/26/packaging-branding-and-themes-for-deployment-in-sharepoint-environments/

Next post – Resource File Deployment (Resx) to the App_GlobalResources path…\

Thanks to Joe McPeak, Ketan Shah for feedback, discussions…

Full source to Timer Jobs and WinForm application for job submission here:

SharePointTimerJobSlnV3.zip

Posted: 01-31-2010 10:32 AM by cicorias | with 1 comment(s)
Filed under: ,
Learning another language…

We’ve been bombarded by Rosetta Stone commercials, magazine adds, and airport kiosks.

I can’t say I picked up much from High School Spanish – in fact, mostly picked it up pickin peaches on a farm.

Here’s a place you can get your feet wet for free…

Livemocha: Language Learning with Livemocha | Learn a Language Online - Free!

Posted: 01-28-2010 5:27 PM by cicorias | with no comments
Filed under:
No, your local admin account is NOT a SQL sysadmin – automatically

There have been many times that I’ve been handed a SQL instance that doesn’t allow me to do much with it even though the IT Admin for the machine have indicated they’ve done the install and your AD account is part of the Local Admin group.

By default, in SQL 2008 local admin’s are not part of the sysadmin role – unless you’re running in single user mode.

So, this article has a quick HOWTO on getting access back so you can go on your merry sysadmin way…

http://technet.microsoft.com/en-us/library/dd207004.aspx

The trick is, restart with the “-m;” parameter, make the change, the change it back (don’t forget that part).  Also, make sure all the other services aren’t running when you need to make this change, otherwise, they connect as THE single user.

How about an upgrade - and don’t turn off Custom Errors..

Here I am just doing some banking and low and behold the bank I work with seems to be rolling out some “Pilot” stuff.

From the error below, I quickly determine that they’re way overdue for moving to a later version of the Framework (I mean 1.1 come on!!!).

The second thing is, why the heck aren’t you trapping these errors instead of giving me the “debug” mode view that only a developer should see!!  And before you write your web.config, at least make sure it’s XML compliant..

How do you get the amateurs out of this business…

image

I’m with COCO….

Definitely a surprise to hear that NBC would even consider all the late night shuffle after all the crap he’s had to wait for.  In support of Conan O’Brien…

http://www.facebook.com/imwithcoco

Posted: 01-13-2010 3:32 PM by cicorias | with no comments
Filed under:
Don’t get caught with long Account Names!!

This has bitten me a couple of times. 

This error surfaced yesterday when running a scripted install with psconfig.exe – the error that appears in the log is:

LookupAccountName failed to get the SID for account <domain>\LONG AC NAME > 20 chars

When setting up SharePoint, we usually have a bunch of service accounts that generally are setup by different teams that manage the Active Directory accounts – well, that’s how it should work, but that’s another story.

Many times organizations will have naming standards.  These naming standards, well, that’s up the the organization and frankly I don’t always agree with them given the ability to have containers in LDAP/AD, but that’s a long discussion.

Sometimes (actually many times) these standards generate long account names.  Can be 20+ characters or more.  The thing is, when you setup an account in AD, it will truncate the “samAccountName” attribute for the user object based upon the Distinguished Name of the account.  So, it usually chops off the end of the string.  I’m not quite sure what it does if if finds a collision with an existing samAccountName and if it has some algorithm to generate a unique on the fly version that doesn’t collide – not my problem…

So, what you just need to do is get the real samAccountName from AD and rerun your script, command, etc. with the corrected <domain>\samAccountName parameter and all is good!!

What Identity with IIS7 and ApplicationPoolIdentity?

With IIS7, we have a little bit more isolation with AppPool Identities

For example, if you’re using the the DefaultAppPool, if you need to assign permissions to NTFS, SQL, etc., what you’d use instead is as follows:

IIS APPPOOL\DefaultAppPool

Where “DefaultAppPool” ends up being the name of your AppPool as shown in IIS Manager…

That is if you’ve set the identity to the “ApplicationPoolIdentity” instead of something else, such as Network Service, Local System, or something other…

Wildcard Certificates and IIS7

Let’s face it, during development, managing all the certificates if you’re doing anything with validating SSL/TLS traffic is a pain.

Now with Windows Identity Foundation (fka Geneva) we really have to get crackin on getting used to managing certificates, setting up SSL sites, etc.

So, here’s great post on setting up IIS7 to use wildcard certificates…

http://blog.mikeobrien.net/PermaLink,guid,12d9628c-a350-4f7b-a573-9d05429b54e8.aspx

This gives you 1 certificate rooted at some common domain (eg.    mydev.local) where all sites would be site1.mydev.local, site2.mydev.local, etc.  The CN in the certificate ends up being *.mydev.local – just wire up in your hosts file (..\drivers\etc\hosts) and you’re golden…

Posted: 12-23-2009 1:28 PM by cicorias | with no comments
Filed under: , ,
Make sure you copy the correct web.config…

During an installation issue, a client followed the TechNet article (http://technet.microsoft.com/en-us/library/cc298447.aspx) and those instructions are misleading.

It indicates to copy the “web.config” to the Layouts directory – what if fails to specify is it should be the “layoutsweb.config” file instead.

While following the article does get you passed the issue that brought you there in the first place, you eventually end up with issues on provisioned sites that reference anything in _layouts are there are sections “<system.web>” that don’t make sense in a IIS path.

Just recopying that correct config file fixes all.

How did I miss this one – The VirtualPathUtility in ASP.NET

I’ve been writing one off methods that apparently are handled quite well for a few things in the type System.Web.VirtualPathUtility.

For example, if you just want the page name or the extension, you have this utility to quickly get that from the current request path.

http://msdn.microsoft.com/en-us/library/system.web.virtualpathutility.aspx

Posted: 12-04-2009 10:11 AM by cicorias | with no comments
Filed under:
Snip-IT Pro – manage all those little code snippets for re-use and sharing…

An old colleague of mine from Avanade has apparently put out a very cool tool for managing re-usable “snippets” of code from a repository.  This is a tool that could be used every day, or even for demos…

This is a smooth application that takes advantage of some the P&P smart client framework.  Quite a professional tool…

The name of the tools is Snip-IT Pro

http://www.snipitpro.com/index.html

It can also managed / retrieve snippets from a hosted service called http://snipplr.com/ along with

Frankly, I can see using this with a simple folder on Mesh (www.Mesh.com)

Posted: 11-30-2009 2:41 PM by cicorias | with no comments
Filed under: ,
SharePoint 2010 and Claims Based Awareness

The industry it moving towards identity standards, and with the recent release of Windows Identity Foundation (fka Geneva), and the beta of SharePoint 2010, it’s important to take a look at the direction of how identity is being normalized into a “service” within the SharePoint object model.

With SPS 2010, the SPUser object is now a claims identity.  Identity management has been normalized to a approach that internally uses an STS that takes all “provider” or external STS identities, then creates a SPUser claims identity.  This can have implications for LOB application design.  Even Windows identities are presented within SPS as a claims identity after banging against the SP STS for claims transformation.

Venky Veeraraghavan has a great video up on Channel 9 on how WIF was used to create this model within SharePoint and how we get 1) Identities “In”, 2) Identities “within”, and 3) Identities “out” – specifically, when talking to downstream back-end LOB applications, DB, Web Services, etc.  These are all things WIF and claims based identity is moving the industry. 

This is certainly how we all should be looking at identity management and authentication scenarios.

 

Click here to play this video

 

Posted: 11-23-2009 8:47 AM by cicorias | with no comments
Filed under: ,
SharePoint 2007 and InfoPath 2010 Client – current install issues…

Ok, I’ve been bitten twice in the past week on this.  If you have InfoPath 2010 Beta installed and you’ve also got SharePoint 2007 running, at least with the latest SP2 and October CU, you run into an issue that surfaces in the logs as follows

One or more types failed to load. Please refer to the upgrade log for more details

When you pull that log apart, you’ll see that it’s attempting to load a few types such as Microsoft.Office.InfoPath.Server.DocumentLifetime.XmlFormHost' from assembly 'Microsoft.Office.InfoPath.Server.

Which, they fail – removing InfoPath from the install works.

Posted: 11-17-2009 10:24 AM by cicorias | with no comments
Filed under:
Loopback Check on Windows 2008, etc.

This KB article (KB926642) explains 2 methods for handling the scenarios that we as developers require for using a local machine for development.  My option has been, going forward, of being explicitly in the host names that my development machine will use.  From that article:

Method 1 (recommended): Create the Local Security Authority host names that can be referenced in an NTLM authentication request

To do this, follow these steps for all the nodes on the client computer:
  1. Click Start, click Run, type regedit, and then click OK.
  2. Locate and then click the following registry subkey: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0
  3. Right-click MSV1_0, point to New, and then click Multi-String Value.
  4. In the Name column, type BackConnectionHostNames, and then press ENTER.
  5. Right-click BackConnectionHostNames, and then click Modify.
  6. In the Value data box, type the CNAME or the DNS alias, that is used for the local shares on the computer, and then click OK.
    Note Type each host name on a separate line.
    Note If the BackConnectionHostNames registry entry exists as a REG_DWORD type, you have to delete the BackConnectionHostNames registry entry.
  7. Exit Registry Editor, and then restart the computer.
More Posts Next page »