One of the great features of Claims authentication in SharePoint (2010 or 2013) is the ability to use external authentication providers such as ADFS, Microsoft LiveID (Hotmail, Outlook.com etc) or even Google among others. Better yet, using Microsoft Azure ACS makes setting up and managing this for extranet sites or Cloud applications simple!

 

The Catch!

However there’s one catch (isn’t there always?) Microsoft Live ID doesn’t give the email address in the claim. This sounds obscure but in effect what it means is that once you configure all of this this is what you get:

image

What a nice and friendly username!

(If you want instructions on configuring all this, I highly recommend Wictor Wilén’s indispensible series of articles on the topic: http://www.wictorwilen.se/Post/Visual-guide-to-Azure-Access-Controls-Services-authentication-with-SharePoint-2010-part-1.aspx)

 

Solutions?

Most people suggest using something like Google to authenticate instead to avoid this, however with SharePoint Online, Project Online, EPMonDemand  (ahem – excuse the blatant plug!) and Office365 all tied to my LiveID, I personally don’t think that’s an option!

So basically we need to populate that information, sure we could ask users to do it manually, but better yet let’s find out how to do this the Microsoft way.

 

Live Connect API

In order to support Windows 8 apps, Web apps and all manner of mobile devices MS have this great resource available, head over to the Live Connect Developer Center to see more information. Once you’ve registered then you can follow my steps below to get you setup to auto-populate SharePoint user information via Javascript!

 

Getting this going

Note: Getting to the business end here; you’ll Visual Studio, some JavaScript, SharePoint Object Model and WebPart development experience to continue. (Or just skip to the end and download my example, like everyone else!)

Firstly you’ll need to create yourself an ‘application’ on Live Connect, something like the following:

image

The important thing is to note your Client ID, and then enter the correct URL under Redirect domain.

All done, easy!

 

If you want to see how all this works, the Interactive Live SDK is one of the best SDK implementations I’ve seen from MS, have a look at what kind of options are available to you.

For example, the following JavaScript will return the basic user information for the currently logged in user including name, email addresses and more;

 1: WL.init({
 2:     client_id: “***********”,
 3:     redirect_uri: “callback.aspx”,
 4:     response_type: “token”
 5: });
 6: WL.login({ “scope”: “wl.emails” }).then(
 7:     function (response) {
 8:         updateUserData();
 9:     },
 10:     function (response) {}
 11: );
 12: function updateUserData() {
 13:     WL.api({ path: “/me”, method: “GET” }).then(
 14:         function (response) {
 15:             // Do something here!
 16:         },
 17:         function (response) {}
 18:     );
 19: }

 

Neat hey?

 

Bring it all together

Okay not quite done yet, lets put this into a nice simple SharePoint webpart and then using the good old Object Model update the current users’ details.

What you need to do:

  1. Open Visual Studio and create a new SharePoint – Visual Web Part Project

image

  1. Create some ASP controls for your webpart, I won’t bore you with all the details here, get the full solution below in which I created the following:
    1. AccountTextBox
    2. NameTextBox
    3. EmailTextBox1
    4. UpdateButton
    5. Plus a bunch of labels to make things neat.
  2. We need a link to the Live Connect http://js.live.net/v5.0/wl.jsfile, to make this easier I have saved a copy in a Layouts folder in my solution, so my ScriptLink looks like this:
    <SharePoint:ScriptLink ID="LiveIdscript" runat="server" Name="UpdateUserWebPart/wl.js"></SharePoint:ScriptLink>
  3. Also for the Live ID bit to work we need a callback.aspx file in our solution, which is referenced in the “redirect_uri” parameter passed to to WL.init in the JavaScript, this file should look something like the following;
 1: <!DOCTYPE html>
 2: <html xmlns="http://www.w3.org/1999/xhtml">
 3: <head>
 4: <title>Authentication Complete</title>
 5: </head>
 6: <body>
 7: <!--
 8: The script will handle passing oauth/authorize response back to the calling page.
 9: -->
 10: <script src="/_layouts/15/UpdateFromLiveID/js/wl.js" type="text/javascript">
 11: </script>
 12: <p>This window can be closed.</p>
 13: </body>
 14: </html>

 

  1. Now for the JavaScript, first we need to initialise and try to login to Windows Live.
 1: <SharePoint:ScriptBlock ID="ScriptBlock1" runat="server">
 2:     WL.init({
 3:         client_id: "*************",
 4:         redirect_uri: "<%=SPContext.Current.Web.Url %>/_layouts/15/UpdateUserWebPart/callback.aspx",
 5:         response_type: "token"
 6:     });
 7:     if (document.getElementById("<%=EmailTextBox1.ClientID %>").value == '') {
 8:         WL.login({ "scope": "wl.emails" }).then(
 9:         function (response) {
 10:             updateUserData();
 11:         },
 12:         function (response) {}
 13:         );
 14:     }

 

Above I’ve included an IF that checks for an existing Email address, if none is found then it automatically tries to login. (Pop-up blockers hate this, so you’ll need to do something nicer)

  1. Next here’s my updateUserData() function which is called on successful login;
 1:     function updateUserData() {
 2:     WL.api({ path: "/me", method: "GET" }).then(
 3:         function (response) {
 4:             document.getElementById("<%=EmailTextBox1.ClientID %>").value = response.emails.preferred;
 5: 
 6:             if (!response.name) {
 7:                 document.getElementById("<%=NameTextBox.ClientID %>").value = response.emails.preferred;
 8:                 }
 9:             else {
 10:                 document.getElementById("<%=NameTextBox.ClientID %>").value = response.name;
 11:             }
 12: 
 13:             document.getElementById("<%=UpdateButton.ClientID %>").click();
 14:             },
 15:             function (response) {}
 16:         );
 17:     }
 18: </SharePoint:ScriptBlock>

 

So once we have the user data we update our ASP controls created previously so we can use those in the code behind.

  1. Lastly we need some code behind to get the details and update our SPUser object.
 1: protected void Page_Load(object sender, EventArgs e)
 2: {
 3:     if (string.IsNullOrEmpty(AccountTextBox.Text))
 4:     {
 5:         AccountTextBox.Text = SPContext.Current.Web.CurrentUser.LoginName;
 6:         NameTextBox.Text = SPContext.Current.Web.CurrentUser.Name;
 7:         EmailTextBox1.Text = SPContext.Current.Web.CurrentUser.Email;
 8:     }
 9: 
 10:     UpdateButton.Click += UpdateButton_Click;
 11: }

 

  1. Here I’m updating the controls on Page_Load but only once, then creating an event for our Button.
  2. For the button I basically do the following:
 1: void UpdateButton_Click(object sender, EventArgs e)
 2: {
 3:     [...]
 4:     SPUser currentUser =
 5:         elevatedWeb.SiteUsers.GetByID(SPContext.Current.Web.CurrentUser.ID);
 6: 
 7:     currentUser.Name = NameTextBox.Text;
 8:     currentUser.Email = EmailTextBox1.Text;
 9: 
 10:     currentUser.Update();
 11:     [...]
 12: }

 

To keep this short(er) I have cut out the RunWithElevatedPrivileges bits and such, you actually might not need to have that depending on your user permissions, but if you leave it in then I suggest reading this.

 

Are we done yet?

Now lets deploy and have a look;

image

 

That’s what we see initially, but once we allow that popup:

image

image

 

Make sure you select Yes.

image

 

Nice.

Now if everything’s working, clicking Update should do just that!

image

Hurrah!

 

Get my full example solution here built for Visual Studio 2012 in SharePoint 2012, just make sure you update it with your Client ID from Live Connect.

 

Conclusion

It’s not perfect, in fact it’s a way off (think application pages, modal dialogs, mmm), but hopefully this will get you started.

Any questions, post below, but I strongly recommend you play with the Interactive SDK on Live Connect and read the Getting started documentation there if you have any problems with the JavaScript. It took me quite a bit to get all that working together the first time!

, , , ,

A very frequent request I get is how to change the default permissions assigned by Project Server to Project Workspace Sites for all projects, unfortunately this is not something that is configurable in Project Server so the only option out of the box is to disable the permissions sync and manage those permissions manually. Of course if you don’t want Administrators to manage every user manually this is not an option.

This is made more annoying if you want PM’s to be able to manage the site, as by default they only have Contribute like access.

There are a few options that exist out there, notably this one; Adjust the Default Project Web Access Permission Levels, which uses a SharePoint timer job to manage the default Permission Levels. However I have had need for something simpler, and so recently for a customer I implemented the following:

 

Modifying Basic Site Permissions using Project Events:

My solution is by intent extremely simple, so simple that I will include the main piece of code below (as well as having the full solution attached), in short what this code does is the following:

On Publish of any project does the following;

  1. Retrieves the OWNER of the Project
  2. Retrieves the Workspace Site of the Project
  3. Adds the Owner to the default SharePoint role SPRoleType.Administrator

So as you can see it only updates one user’s permissions, however it would be trivial to update the code to do more based on the Project Team and other requirements.

Here is the bulk of the code:

if (wssData.ProjWssInfo.Count == 0)
{
  LogEventEntry(String.Format("No workspace site found for project: {0}", 
    projectData.Project.First().PROJ_NAME), EventLogEntryType.Warning);
}
else {
  // Open the SPSite and web
  using (SPSite pwsSite = new SPSite(wssData.ProjWssInfo[0].PROJECT_WORKSPACE_URL))
  {
    using (SPWeb pwsWeb = pwsSite.OpenWeb())
    {
      // Handle Null Email Address
      String ownerEmail;
      if (resourceData.Resources.First().IsWRES_EMAILNull()) ownerEmail = "";
      else ownerEmail = resourceData.Resources.First().WRES_EMAIL;

      // Add the new role assignment to the Owner
      SPRoleAssignment roleAssn = 
        new SPRoleAssignment(resourceData.Resources.First().WRES_ACCOUNT,
          ownerEmail,
          resourceData.Resources.First().RES_NAME,
          "Project Owner");
      SPRoleDefinition roleDefn = 
        pwsWeb.RoleDefinitions.GetByType(SPRoleType.Administrator);
      roleAssn.RoleDefinitionBindings.Add(roleDefn);

      pwsWeb.RoleAssignments.Add(roleAssn);
    }
  }
}

 

The code is bound to both OnPublished and OnSummaryPublished (to get the Save from PWA also), and ensures that the Owner will always have Admin rights, and if not all the PM has to do is republish.

Download only the WSP package here with – basic – instructions.

Download the full source package here.

Notes:

  • As an Event the solution runs under the security context of the Project Server Event Service account, and so this user must be a user in PWA with appropriate access, I use administrator however it could get by with less.
  • This completely ignores the WssSync jobs created by Project Server, as such some actions such as AD sync and group membership changes can apply permissions outside of typical Publish events. However in my experience with 2010 this will not remove existing SharePoint permissions (see one of my previous blogs on this).
  • The solution is built with Visual Studio 2010 and packaged as a WSP allowing for simple installation using typical SharePoint means.
  • The solution uses Project Server 2010 WCF methods to access the PSI, and so without modification it will not work on Project Server 2007.
  • The solution logs error information to the Event Log and not the ULS, this is because I’m lazy. However it means that on Windows Server 2008 if the user (Event Service Account) is not a local admin it will fail and log nothing.

 

Post comments with any questions or additions even, but please note that I can’t provide support for the standalone WSP package.

, ,

This is a useful solution; with it you can update on a PDP an Excel spreadsheet saved in project’s workspace document library and then potentially update custom fields from the results.

However it is far from a complete solution to download and deploy.

First issue – Admin rights required to use Web-Part:

Installed as per instructions (build / package / run script), then when adding the web part this familiar error occurred:

“An unexpected error has occurred.”

A quick look at the ULS shows more details:

The source was not found, but some or all event logs could not be searched. Inaccessible logs: Security.

Now that’s interesting as I had this exact same issue a few months back working with a 2007 PSI project, it turns out the answer is rather simple; the web service is attempting to write to the event log using “EventLog.WriteEntry” method calls, however in Windows 2008 and above this is restricted to Administrators only. So as the PWA users are not local admins the error was guaranteed.

A quick solution is to comment out each line in the code beginning with “EventLog.” of course that will disable the logging, so you would be better off doing that after you get everything else working. The file in the solution that you are after is CostAndBenefiXlsWebPart.cs in the WebPart.CostBenefit project and you’ll find about half a dozen envent log entries to comment out.

Second issue – The default site template location:

While your in that file commenting out lines, you’ll most likely notice this;

private string pdpSitePath = @”http://project.contoso.com/PWA/MajorPWSTemplateSite;”

Although the solution will work without changing this, as that is only used when no workspace site exists (like when in server settings – project detail pages), updating that to point to your http://server/pwa/template workspace site template will fix any file not found issues when setting up your PDPs.

Third issue – Location of the Excel document used:

If you’ve gotten this far then like me you might be seeing something like this:

No item exists at http://servername/_layouts/xlviewer.aspx?list=b73da3b8-b48e-400c-8fe0-9b6a48442518&id=1&DefaultItemOpen=1&Edit=1.  It may have been deleted or renamed by another user.

A little digging turned up the cause of this one to be the embedded ID in that URL “&id=1”, which means that it is trying to open the document with ID 1 in that particular list. This again is hard coded in the same .cs file as above (line 191), so you can either make certain that in your template above the first file saved to the Project Documents folder is your xls sheet, or modify the code again.

 

All done.

So now we should have a working webpart referencing our Excel sheet saved in the project workspace site, there are some obvious limitations, not least of all the inability to use any other spreadsheet in the webpart. But I guess that’s why they call it a “solution starter”.

 

Update 23/10/2010: Most of the points above have been fixed in the more recent updates to the solution starter package, make sure you update your code from Codeplex: http://code.msdn.microsoft.com/P2010SolutionStarter

, , , ,

I’ve been working on a full end-to-end demand management workflow over the past couple of weeks and unfortunately as RTM is due any day now, much of the final documentation is still not public, meaning that some things just need to be figured out.

One problem that I found is the custom workflow task form, which has a very useful "Review project details" link on it. However if you have used Microsoft’s demo2010a image, then you’d see that for all of the workflow’s the link initially does not work. (See image)

clip_image001

 

Fortunately this one was relatively easy to guess, thanks MS for making it quite obvious!

Firstly a custom workflow task content type called PSWApprovalTask is created by default in the PWA site "Project Server Workflow Tasks" list, here’s what it looks like out-of-the-box:

clip_image002

 

Interestingly though, it is not by default added to the list content types, BUT if you run through the Sample Proposal Workflow once then you will see it appear in the list automatically. For the purpose of this just use the ‘Add from existing site content types’ option in List Settings to add it:

clip_image003

 

Now another interesting thing is apparent on the list settings page:

clip_image004

 

Specifically Microsoft has pre-defined three Columns named: Project Name, Project Owner and ProjectUid (and some others..), however they are not added to the content type by default either, but they are used in the views including the default My Tasks.

To the point:

Now getting to the point of this post, if you use the content type and populate all of those fields then the task approval page will work as expected! Specifically the ProjectUID Column needs to be populated with guess what: the Project’s GUID, once that is done then the link works as expected!

Too easy!

Well unfortunately not. The Visual Studio Workflow Activities included with Project Server 2010 gives you the very handy ReadProjectProperty action, however while having a choice of either reading a Custom Field based on GUID or a pre-defined built in field based on name, it does not include Project GUID. This is where it gets complicated.

The “simple” solution is to use PSI, then using something like this nice little method created by Chris Boyd you can retrieve the GUID by providing the Project Name using PSI method ReadProjectStatus.

Unfortunately having to use PSI in what otherwise could be a very simple SharePoint workflow in my opinion is clunky, it would be very nice if MS could add the Proj_UID property to the ReadProjectProperty action!

If you’re interested, based on Chris’s blog post linked above, here is an extract of my code to get all of those fields working (Note that this plugs in nicely to my last code post: Extending the Branching Workflow):

// Declare your PSI Stuff
private static WebSvcProject.Project project = new WebSvcProject.Project();

private void createTask_InitApproval(object sender, EventArgs e)
{
    [... insert your other required task attributes ...]
    taskProps.ExtendedProperties["Project Name"] = Proj_Name[0];
    taskProps.ExtendedProperties["Project Owner"] = Proj_Owner_Name[0];
    taskProps.ExtendedProperties["ProjectUid"] = GetProjectUidFromProjectName(Proj_Name[0]);
}
public static Guid GetProjectUidFromProjectName(string projectName)
{
    Guid projectGUID;
    project.Credentials = CredentialCache.DefaultCredentials;
    WebSvcProject.ProjectDataSet readProjDs = new WebSvcProject.ProjectDataSet();
    readProjDs = project.ReadProjectStatus(
        Guid.Empty,
        WebSvcProject.DataStoreEnum.WorkingStore,
        projectName,
        0
        );
    if (readProjDs.Project.Rows.Count == 1)
    {
        projectGUID = new Guid(readProjDs.Project[0].PROJ_UID.ToString());
    }
    else
    {
        throw new Exception("No Project by the name: " + projectName + " Found");
    }
    return projectGUID;
}

Enjoy!

Update 4/08/2010:Check the comments below for a much easier way to correct this issue, without the use of PSI.

, , ,

With Project Server 2010 you get an out of the box workflow named ‘Sample Proposal Workflow’, this is quite a good demonstration as it includes all of the usual things you might see in such a process; Validation, Approval, Selection, etc. However unfortunately as of this time (days before RTM) the source for this sample is still not available, at the moment the only SDK example is a simple Branching Workflow which only includes a single validation step.

I had an opportunity at the Sydney Ignite to ask Jan Kalis about this one, and he assured me that the full source for the sample will be released sometime around RTM, but we’ll have to wait a little bit more for that one.

In the meantime the world moves on and I have been busily writing my first full blow demand management workflow for a customer deployment, and so I thought I would share some of what I have done here. To that end I have taken the SDK Branching Workflow and extended it to include an Approval stage similar to that in the Sample Proposal Workflow.

Read below for what I have done and what you need to test it for yourself, then download my Visual Studio 2010 project below.

Branching Workflow with Approval:

The steps below are a summary of the changes made to the Branching Workflow, for a more complete how-to guide I very strongly suggest that you read the SDK Branching Workflow article and if you complete the examples there you will end where my steps below begin. Furthermore all the general requirements for getting this running (Visual Studio 2010 etc) are in the SDK article.

Before you start (or download):

This example is built on a default PWA instance, I have renamed the out of the box Sample Proposal Workflow to ‘Branching Workflow with Approvals’ then after deploying the code changed the workflow selected to the new one. All other PDPs and Stages are used as is, and the only other change required is an optional one below in step 5 below (see image).

Changes Made:

  1. Starting with the SDK Branching Workflow (note that if you download it in the p14betasdk then the code is incomplete and a few steps from the MSDN article must be completed). I also renamed the Feature and the Workflow in the deployment package to avoid confusion.
  2. Then I added a pre-build event command to automatically regenerate the Strong Name. (Can be removed, but just makes testing easier)
  3. Added a step after the Proposal Details Stage where the workflow goes to Proposal Selection Stage in Select Phase (updateProjectStageStatus20 – Waiting for Approval) – Note I have used the Select phase as my ‘Approval’ phase, I am not doing anything with Portfolio Selection here just approval.
  4. Next I read the Project Properties of "Project Name" and "Owner" for the Workflow Task (These are optional and just for completeness).
  5. Read "Portfolio Managers" Project Server Group membership to create approval request for first person returned in group.
  6. Create Workflow Task using CreateTaskWithContentType and assign to approver and populate task properties with Project Name and Owner.

Important: In order for these task properties to work you must ad the built in Columns to the PSWApprovalTask content type by editing the Default Workflow Approvals, then updating the content type to include the existing list columns.

(See Screenshot from List Settings PSWApprovalTask Content Type Properties)

clip_image001

  1. Then we add a while activity onTaskChanged to wait for the Approval task to be actioned. In this step the ExtendedProperties are checked for a value "Approved" anything else results in Rejection.
  2. And finally an IfElseActivity is used to branch on the Approved / Rejected result.
    1. If true then continue to end and UpdateProjectStageStatus to Execution stage.
    2. Else UpdateProjectStageStatus to Not Selected state and Terminate.

Here is the result in Visual Studio:

BranchingWorkflowApproval

What’s left?

  • Email notification? You can simply use the SharePoint list notifications on the Workflow Task list as a simple and consistent email notification method.
  • Portfolio Selection? I’ll leave that one to you.

 

Technical Details:

This example was created on the following beta software, meaning that things might change by RTM:

  • SharePoint Server 2010 RC Build
  • Project Server 2010 RC Build
  • Visual Studio 2010 RC Build

Download the full project files in a zip here. (*see update below)

 

MSDN References:

How to: Create a Branching Workflow <http://msdn.microsoft.com/en-us/library/ee767701(v=office.14).aspx>

Step 3: Create the Workflow <http://msdn.microsoft.com/en-us/library/ms580283.aspx>

 

UPDATE 3/09/2010:

I thought that I’d revisit this one as clearly from the comments there was something wrong after RTM, basically the problem everyone seems to be having relates to the ‘WorkflowTaskCTypeID’ defined. Somehow what I had in the BranchingWorkflow.cs file is actually incorrect, the solution has the following on line 46:

public String WorkflowTaskCTypeID = “0x0108010038A52C27344148C9B9214F82C7C0298500544602C73FFD1245BCC090442C85426B”;

When it should be:

public String WorkflowTaskCTypeID = “0x0108010038A52C27344148C9B9214F82C7C02985?;

Not quite sure where the first one came from (and why it worked?!) but that fixed it for me. If you update that line then recompile / rebuild you should be good.

 

PS. I don’t use the CreateTaskWithContentType anymore, have a look at the 2010 workflow activity (OfficeTask class) used in the Sample Proposal found in the SDK: http://msdn.microsoft.com/en-us/library/microsoft.office.workflow.actions.officetask.aspx

, , ,

While diving deeper into the SharePoint 2010 PowerShell command-lets I realised that my last blog using a borrowed script based on 2007 was in need of a major update for 2010, so see here a version doing the same thing but with no use of STSADM (and thus no need to run as admin in 2008).

Warmup2.ps1

############################################################################
#WarmUp2.ps1 - Enumerates all web sites in web applications in a 2010
# SharePoint farm and opens each in a browser.
#Notes:
#-"get-webpage" function borrowed from:
# http://kirkhofer.wordpress.com/2008/10/18/sharepoint-warm-up-script/
#
#Assumptions:
#-Running on machine with WSS/MOSS 2010 installed
############################################################################
 
Add-PsSnapin Microsoft.SharePoint.PowerShell
$extrasitelistfile = 'c:\Tools\Warmup\warmup-extrasites.txt'
 
function get-webpage([string]$url,[System.Net.NetworkCredential]$cred=$null)
{
  $wc = new-object net.webclient
  if($cred -eq $null)
  {
    $cred = [System.Net.CredentialCache]::DefaultCredentials;
  }
  $wc.credentials = $cred;
  return $wc.DownloadString($url);
}
 
#This passes in the default credentials needed. If you need specific
#stuff you can use something else to elevate basically the permissions.
#Or run this task as a user that has a Policy above all the Web
#Applications with the correct permissions
 
$cred = [System.Net.CredentialCache]::DefaultCredentials;
#$cred = new-object System.Net.NetworkCredential("username","password","machinename")
 
$apps = get-spwebapplication -includecentraladministration
foreach ($app in $apps) {
  $sites = get-spsite -webapplication $app.url
  foreach ($site in $sites) {
    write-host $site.Url;
    $html=get-webpage -url $site.Url -cred $cred;
  }
}
# Warm up other sites specified in warmup-extrasites.txt file (such as SSRS)
 
if (test-path $extrasitelistfile) {
  $extrasites = get-content $extrasitelistfile
  foreach ($site in $extrasites) {
    write-host $site;
    $html=get-webpage -url $site -cred $cred;
  }
}

The script still retrieves a list of all sites in all web applications in the farm (including Central Admin) and additionally retrives a list of sites in a text file by default located in “C:\Tools\Warmup\warmup-extrasites.txt”
 
Warmup-extrasites.txt
http://servername/ReportServer
Enjoy!

, ,

[Update: 23/02/10] I have updated this script in a new post here which does not rely on STSADM and therefore does not need to be run as admin.

With all the 2010 lab testing I have been doing lately I’ve been meaning to get around to creating / finding a new WarmUp script to use but this time based on PowerShell.

What I found is fortunately there are loads of examples out there to get you started as with most things PowerShell. See Kirk Hofer’s Blog for the one I started with.

So anyway I have added a little to the script, now in addition to opening each site in the farm it also parses a text file (C:\Tools\Warmup\warmup-extrasites.txt) for additional URL’s, which I use to warm up particular SSRS or Excel reports.

Warmup.ps1

############################################################################
#Assumptions:
#-Running on machine with WSS/MOSS
############################################################################
 
$stsadmexe = 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN\STSADM.exe'
$extrasitelistfile = 'c:\Tools\Warmup\warmup-extrasites.txt'
 
function get-webpage([string]$url,[System.Net.NetworkCredential]$cred=$null)
{
  $wc = new-object net.webclient
  if($cred -eq $null)
  {
    $cred = [System.Net.CredentialCache]::DefaultCredentials;
  }
  $wc.credentials = $cred;
  return $wc.DownloadString($url);
}
 
#This passes in the default credentials needed. If you need specific
#stuff you can use something else to elevate basically the permissions.
#Or run this task as a user that has a Policy above all the Web
#Applications with the correct permissions
 
$cred = [System.Net.CredentialCache]::DefaultCredentials;
#$cred = new-object System.Net.NetworkCredential("username","password","machinename")
 
[xml]$x=&$stsadmexe -o enumzoneurls
foreach ($zone in $x.ZoneUrls.Collection) {
  [xml]$sites=&$stsadmexe -o enumsites -url $zone.Default;
  foreach ($site in $sites.Sites.Site) {
    write-host $site.Url;
    $html=get-webpage -url $site.Url -cred $cred;
  }
}
 
# Warm up other sites specified in warmup-extrasites.txt file (such as SSRS)
 
if (test-path $extrasitelistfile) {
  $extrasites = get-content $extrasitelistfile
  foreach ($site in $extrasites) {
    write-host $site;
    $html=get-webpage -url $site -cred $cred;
  }
}

Warmup-ExtraSites.txt
http://servername/ReportServer

Download both files here.

Take note of the variables at the top of the script with the paths pointing to the 14 hive and the extra sites text file.

Save those files somewhere (default c:\Tools\WarmUp\) then schedule them to run with a command line as follows:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe C:\Tools\Warmup\warmup.ps1

Finally just make sure you select the Run with highest privileges option as stsadm will need that to enumerate the sites.

Enjoy!

, , ,