SharePoint Automation Gary Lapointe – Founding Partner, Aptillon, Inc.

26Jun/118

Getting (and taking ownership of) Checked Out Files using Windows PowerShell

Often when I’m working on a project I need to generate a list of all checked out files and provide that to my client just prior to release to production. Sometimes the client will manually inspect each of them and act as they see fit and other times they’ll ask me to just batch publish all of them (for which I use my Publish-SPListItems cmdlet). So, how do I generate the report for the client? It’s actually pretty easy using PowerShell and a couple of quick loops. Here’s an example that loops through every Site Collection in the Farm and generates a nice report:

function Get-CheckedOutFiles() {
    foreach ($web in (Get-SPSite -Limit All | Get-SPWeb -Limit All)) {
        Write-Host "Processing Web: $($web.Url)..."
        foreach ($list in ($web.Lists | ? {$_ -is [Microsoft.SharePoint.SPDocumentLibrary]})) {
            Write-Host "`tProcessing List: $($list.RootFolder.ServerRelativeUrl)..."
            foreach ($item in $list.CheckedOutFiles) {
                if (!$item.Url.EndsWith(".aspx")) { continue }
                $hash = @{
                    "URL"=$web.Site.MakeFullUrl("$($web.ServerRelativeUrl.TrimEnd('/'))/$($item.Url)");
                    "CheckedOutBy"=$item.CheckedOutBy;
                    "CheckedOutByEmail"=$item.CheckedOutByEmail
                }
                New-Object PSObject -Property $hash
            }
            foreach ($item in $list.Items) {
                if ($item.File.CheckOutStatus -ne "None") {
                    if (($list.CheckedOutFiles | where {$_.ListItemId -eq $item.ID}) -ne $null) { continue }
                    $hash = @{
                        "URL"=$web.Site.MakeFullUrl("$($web.ServerRelativeUrl.TrimEnd('/'))/$($item.Url)");
                        "CheckedOutBy"=$item.File.CheckedOutByUser;
                        "CheckedOutByEmail"=$item.File.CheckedOutByUser.Email
                    }
                    New-Object PSObject -Property $hash
                }
            }
        }
        $web.Dispose()
    }
}
Get-CheckedOutFiles | Out-GridView

Running the above will generate a fairly nice report with URLs and usernames and whatnot; you could also use the Export-Csv cmdlet to dump the results to a CSV file that you can then hand off to your end-users. One cool thing to point out about this is that it will also show you files that you normally can’t see – that is files that have been created by other users but have never had a check in. This is actually pretty cool and I stumbled upon this when trying to fine tune my Publish-SPListItems cmdlet. You see, if the file has never been checked in then iterating through the SPListItemCollection object will not reveal the item (or file I should say); this meant that my cmdlet, as it was previously written, was missing a bunch of files. So to work around this all I had to do was add an additional loop to iterate over the collection returned by the SPDocumentLibrary’s CheckedOutFiles property. For each SPCheckedOutFile object in that collection I then call TakeOverCheckOut() to grab the checked out file so that I can then publish.

I use this enough that I decided to turn it into a cmdlet that is now part of my custom extensions. Like the above script, I return back a custom object that contains the full URLs and other  useful information (such as the List, Site, and Site Collection identifiers). I also exposed a TakeOverCheckOut() and Delete() method which simply calls Microsoft’s implementation of those methods.

I called this cmdlet Get-SPCheckedOutFiles (note that I’d previously released this cmdlet under the name Get-SPFilesCheckedOut but have since reworked and renamed that original implementation).

Here’s the full help for the cmdlet:

PS C:\Users\spadmin> help Get-SPCheckedOutFiles -full

NAME
Get-SPCheckedOutFiles

SYNOPSIS
Retrieves check out details for a given List, Web, or Site.

SYNTAX
Get-SPCheckedOutFiles [-Site] <SPSitePipeBind> [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]

Get-SPCheckedOutFiles [-Web] <SPWebPipeBind> [-ExcludeChildWebs <SwitchParameter>] [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]

Get-SPCheckedOutFiles [[-Web] <SPWebPipeBind>] [-List] <SPListPipeBind> [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]


DESCRIPTION
Retrieves check out details for a given List, Web, or Site.

Copyright 2010 Falchion Consulting, LLC
> For more information on this cmdlet and others:
>
http://blog.falchionconsulting.com/
> Use of this cmdlet is at your own risk.
> Gary Lapointe assumes no liability.

PARAMETERS
-Site <SPSitePipeBind>
Specifies the URL or GUID of the Site to inspect.

The type must be a valid GUID, in the form 12345678-90ab-cdef-1234-567890bcdefgh; a valid URL, in the form
http://server_name; or an instance of a valid SPSite object.

Required? true
Position? 1
Default value
Accept pipeline input? true (ByValue, ByPropertyName)
Accept wildcard characters? false

-Web <SPWebPipeBind>
Specifies the URL or GUID of the Web to inspect.

The type must be a valid GUID, in the form 12345678-90ab-cdef-1234-567890bcdefgh; a valid URL, in the form
http://server_name; or an instance of a valid SPWeb object.

Required? true
Position? 1
Default value
Accept pipeline input? true (ByValue, ByPropertyName)
Accept wildcard characters? false

-List <SPListPipeBind>
The list whose checked out files are to be returned.

The value must be a valid URL in the form
http://server_name/lists/listname or /lists/listname. If a server relative URL is provided then the Web parameter must be provided.

Required? true
Position? 1
Default value
Accept pipeline input? true (ByValue, ByPropertyName)
Accept wildcard characters? false

-ExcludeChildWebs [<SwitchParameter>]
Excludes all child sites and only considers the specified site.

Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false

-AssignmentCollection [<SPAssignmentCollection>]
Manages objects for the purpose of proper disposal. Use of objects, such as SPWeb or SPSite, can use large amounts of memory and use of these objects in Windows PowerShell scripts requires proper memory management. Using the SPAssignment object, you can assign objects to a variable and dispose of the objects after they are needed to free up memory. When SPWeb, SPSite, or SPSiteAdministration objects are used, the objects are automatically disposed of if an assignment collection or the Global parameter is not used.

When the Global parameter is used, all objects are contained in the global store. If objects are not immediately used, or disposed of by using the Stop-SPAssignment command, an out-of-memory scenario can occur.

Required? false
Position? named
Default value
Accept pipeline input? true (ByValue)
Accept wildcard characters? false

<CommonParameters>
This cmdlet supports the common parameters: Verbose, Debug, ErrorAction, ErrorVariable, WarningAction, WarningVariable, OutBuffer and OutVariable. For more information, type, "get-help about_commonparameters".

INPUTS

OUTPUTS

NOTES

For more information, type "Get-Help Get-SPCheckedOutFiles -detailed". For technical information, type "Get-Help Get-SPCheckedOutFiles -full".

------------------EXAMPLE------------------

PS C:\> Get-SPCheckedOutFiles -Site "
http://server_name/"


This example outputs a list of files that are checked out for the given Site Collection


RELATED LINKS
Get-SPFile

 

In the following example I’m retrieving pages from the root Pages library that are checked out:

image

In this example I am running the cmdlet as the aptillon\spadmin user and I’m now able to see the checkout by the user aptillon\glapointe. I ran the cmdlet twice so you could see the default tabular view as well as the more detailed view. Again, you could easily use the Export-Csv cmdlet to dump this information to a file that you can provide your end-users.

I hope you find this cmdlet useful – it personally has proven invaluable to me, particularly when working on anonymous access internet sites as end-users are notorious about creating pages and not getting them checked in.

P.S. With this release the Publish-SPListItems cmdlet has been updated to now consider files that don’t have any existing check-ins.

6Jul/091

Deploying SharePoint Files Not Handled by the WSP Solution Schema

I was working on a project recently where I had to deploy a settings file to the root of my web applications folder (where the web.config file resides).  If you've ever had to do something like this before then you know that you cannot do this declaratively using the WSP's Solution schema.  The Solution schema is really quite limiting as to where you can actually deploy files - as a result your only option is to create a custom Feature that runs some code when executed (because we certainly don't want to go the xcopy route).

To do this we're going to create a custom Feature which contains all the files that we need to copy and then we'll provision a one-time timer job to copy the file to the target location on each server.

Here's our Feature.xml file:

<?xml version="1.0" encoding="utf-8"?>
<Feature
Id="1960C4A0-7A47-42A8-A382-F7A91214BA39"
Title="Settings Provisioner"
Description="This Feature deploys a settings file to a the web application root."
Version="1.0.0.0"
Scope="WebApplication"
Hidden="false"
ReceiverAssembly="MyCustomFeature, Version=1.0.0.0, Culture=neutral, PublicKeyToken=39b13c54ceef5193"
ReceiverClass="MyCustomFeature.FeatureReceivers.SettingsFeatureReceiver" xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementFile Location="Files\settings.config" />
</ElementManifests>
</Feature>

As you can see we are including a "settings.config" file which is located in a folder called "Files" directly under the Feature folder.  You could easily have any number of files here by simply adding additional ElementFile elements.  Also note that we are linking a feature receiver to the Feature which will execute upon activation and deactivation.

Here's our feature receiver class:

   1: public class SettingsFeatureReceiver : SPFeatureReceiver
   2: {
   3:     /// <summary>
   4:     /// Occurs after a Feature is activated.
   5:     /// </summary>
   6:     /// <param name="properties">An <see cref="T:Microsoft.SharePoint.SPFeatureReceiverProperties"></see> object that represents the properties of the event.</param>
   7:     public override void FeatureActivated(SPFeatureReceiverProperties properties)
   8:     {
   9:         SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
  10:         try
  11:         {
  12:             TimerJobs.CopySettingsJob job = new TimerJobs.CopySettingsJob(webApp);
  13:             job.SubmitJob(true, properties.Feature.Definition.RootDirectory);
  14:         }
  15:         catch (Exception ex)
  16:         {
  17:             Logger.WriteException(ex);
  18:         }
  19:  
  20:         
  21:     }
  22:  
  23:     /// <summary>
  24:     /// Occurs when a Feature is deactivated.
  25:     /// </summary>
  26:     /// <param name="properties">An <see cref="T:Microsoft.SharePoint.SPFeatureReceiverProperties"></see> object that represents the properties of the event.</param>
  27:     public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
  28:     {
  29:         try
  30:         {
  31:             TimerJobs.CopySettingsJob job = new TimerJobs.CopySettingsJob(webApp);
  32:             job.SubmitJob(false, properties.Feature.Definition.RootDirectory);
  33:         }
  34:         catch (Exception ex)
  35:         {
  36:             Logger.WriteException(ex);
  37:         }
  38:     }
  39:  
  40:     public override void FeatureInstalled(SPFeatureReceiverProperties properties)
  41:     {
  42:         /* no op */
  43:     }
  44:     public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
  45:     {
  46:         /* no op */
  47:     }
  48: }

Notice that within the FeatureActivated method I'm getting a reference to the SPWebApplication object and passing that to a CopySettingsJob class which is our timer job that will do all the work.  On the FeatureDeactivating event you can see similar code but I'm passing in false instead of true to the Submit method.  The Boolean value indicates whether we are activating or deactivating our Feature.  I'm also passing in the path to the Feature folder in the 12 hive as that is where our source files are located.

Lets look at the timer job class now:

   1: public class CopySettingsJob : SPJobDefinition
   2: {
   3:     private const string KEY_ACTIVATING = "Activating";
   4:     private const string KEY_FEATUREFOLDER = "FeatureFolder";
   5:     private const string JOB_NAME = "job-settings-copy-";
   6:     private static readonly string jobId = Guid.NewGuid().ToString();
   7:  
   8:     public CopySettingsJob() : base() { }
   9:  
  10:     /// <summary>
  11:     /// Initializes a new instance of the <see cref="CopySettingsJob"/> class.
  12:     /// </summary>
  13:     /// <param name="webApp">The web app.</param>
  14:     public CopySettingsJob(SPWebApplication webApp)
  15:         : base(JOB_NAME + jobId, webApp, null, SPJobLockType.None)
  16:     {
  17:         Title = "Copy Settings Job";
  18:     }
  19:  
  20:     /// <summary>
  21:     /// Executes the job definition.
  22:     /// </summary>
  23:     /// <param name="targetInstanceId">For target types of <see cref="T:Microsoft.SharePoint.Administration.SPContentDatabase"></see> this is the database ID of the content database being processed by the running job. This value is Guid.Empty for all other target types.</param>
  24:     public override void Execute(Guid targetInstanceId)
  25:     {
  26:         Logger.WriteInformation(string.Format("Starting {0} timer job.", Name));
  27:  
  28:         try
  29:         {
  30:             string settingsFilePath = Path.Combine(Properties[KEY_FEATUREFOLDER].ToString(), "Files\\Settings.config");
  31:             string targetPath = Path.Combine(WebApplication.IisSettings[SPUrlZone.Default].Path.ToString(), "Settings.config");
  32:             if ((bool)Properties[KEY_ACTIVATING])
  33:             {
  34:                 Logger.WriteInformation(string.Format("Copying file from \"{0}\" to \"{1}\".", settingsFilePath, targetPath));
  35:                 File.Copy(settingsFilePath, targetPath, true);
  36:             }
  37:             else
  38:             {
  39:                 Logger.WriteInformation(string.Format("Deleting file from \"{0}\"", targetPath));
  40:                 File.Delete(targetPath);
  41:             }
  42:         }
  43:         catch (Exception ex)
  44:         {
  45:             Logger.WriteException(ex);
  46:             return;
  47:         }
  48:         Logger.WriteSuccessAudit(string.Format("Timer job {0} completed successfully", Name));
  49:     }
  50:  
  51:     /// <summary>
  52:     /// Submits the job.
  53:     /// </summary>
  54:     /// <param name="activating">if set to <c>true</c> [activating].</param>
  55:     public void SubmitJob(bool activating, string featureFolder)
  56:     {
  57:         Properties[KEY_ACTIVATING] = activating;
  58:         Properties[KEY_FEATUREFOLDER] = featureFolder;
  59:         Schedule = new SPOneTimeSchedule(DateTime.Now);
  60:         Title += " (" + jobId + ")";
  61:         Update();
  62:     }
  63: }

As you can see the code simply stores the Feature folder as a property and then sets a one-time schedule.  When the code runs it copies the source file to the target.  Because we're using an SPJobLockType value of "None" in the constructor the code will execute on every server (set it to "Job" if you want it to run on just the server in which the Feature was actually activated).

Of course the code above isn't very generic as it hard codes the settings.config file which isn't very reusable but I wanted to keep this sample nice and simple.  A better approach would be to require an either an XML file to be stored in the Feature folder and then read by the timer job or have the SubmitJob method take in parameters that describe what files to move and where to move them.

One key thing to remember is that this code will run once on each server for every web application on which the Feature has been activated.  If you need a Farm scoped Feature because perhaps you are copying the noise words file for instance then you'll want to change the constructor of the timer job to take in an SPService object and change the FeatureActivated method as shown below:

   1: try
   2: {
   3:     string featurePath = properties.Feature.Definition.RootDirectory;
   4:  
   5:     SPTimerService timerService = SPFarm.Local.TimerService;
   6:     if (null == timerService)
   7:     {
   8:         throw new SPException("The Farms timer service cannot be found.");
   9:     }
  10:     TimerJobs.CopySettingsJob job = timerService.JobDefinitions.GetValue<TimerJobs.CopySettingsJob>(TimerJobs.CopySettingsJob.JOB_NAME);
  11:     if (null == job)
  12:     {
  13:         job = new TimerJobs.CopySettingsJob(timerService);
  14:     }
  15:     job.SubmitJob(true, properties.Feature.Definition.RootDirectory);
  16: }
  17: catch (Exception ex)
  18: {
  19:     Logger.WriteException(ex);
  20: }

Hopefully this simple example helps you to solve your file deployment challenges.

18Dec/0724

Save List Items and Files to Disk

I've seen numerous examples of people needing to save all the files from a document library or custom list (containing attachments) to disk. I didn't necessarily need the ability myself for the upgrade we are doing but I did need a quick way to generate lots of different samples to make sure that my gl-addlistitem command was working correctly. So I decided to create a new command which would make my testing easier as well as help the many out there that have the need of saving lots of files out to disk. The command I created is gl-exportlistitem2. I already had an gl-exportlistitem command which used the deployment API and I just wasn't feeling very creative with the name so I just added "2" (maybe "savelistdata" is better???). The command does two key things - saves all the files to a specified path and creates a Manifest.xml file that contains information about the files and any list items that were in the list. This information can then be used by the gl-addlistitem command to actually import the data into another list. For this initial version I've kept things fairly simple - there's no compression, no security information, and no version history. I'm only storing the file(s) (if present) and any field data (perhaps I'll look to handle more data in the future but for now this met my needs). The nice thing is that if you don't need any of the other information then what I created actually works better than using the deployment API as mine actually takes folder location into account whereas the deployment API is extremely buggy when it comes to folders. I also included a simplified version of the code which just simply dumps all the files to disk without the manifest information (the command does not use this but I kept it in the source in case anyone needed it). The code to do all of this is really straightforward - I decided to break it up into two chunks - the first gathers all the necessary data from the list and stores it in some custom data classes and the second takes those classes and saves to disk and creates the actual manifest file:

   1: /// <summary>
   2: /// Gets the item data.
   3: /// </summary>
   4: /// <param name="web">The web.</param>
   5: /// <param name="list">The list.</param>
   6: /// <param name="ids">The ids.</param>
   7: /// <returns></returns>
   8: private static List<ItemInfo> GetItemData(SPWeb web, SPList list, List<int> ids)
   9: {
  10:  List<ItemInfo> itemData = new List<ItemInfo>();
  11:  
  12:  foreach (SPListItem item in list.Items)
  13:  {
  14:   if (!(ids.Count == 0 || ids.Contains(item.ID)))
  15:    continue;
  16:  
  17:   ItemInfo info = new ItemInfo();
  18:   itemData.Add(info);
  19:   info.ID = item.ID;
  20:   
  21:   if (item.File != null)
  22:   {
  23:    info.File = new FileDetails(item.File.OpenBinary(), item.File.Name, item.File.Author, item.File.TimeCreated);
  24:    info.Title = item.File.Name;
  25:   }
  26:   else
  27:    info.Title = item.Title;
  28:  
  29:   info.FolderUrl = item.Url.Substring(list.RootFolder.Url.ToString().Length, item.Url.LastIndexOf("/") - list.RootFolder.Url.ToString().Length);
  30:  
  31:   try
  32:   {
  33:    foreach (string fileName in item.Attachments)
  34:    {
  35:     SPFile file = web.GetFile(item.Attachments.UrlPrefix + fileName);
  36:     info.Attachments.Add(new FileDetails(file.OpenBinary(), file.Name, file.Author, file.TimeCreated));
  37:    }
  38:   }
  39:   catch (ArgumentException)
  40:   {}
  41:  
  42:   foreach (SPField field in list.Fields)
  43:   {
  44:    if (!field.ReadOnlyField && 
  45:     field.InternalName != "Attachments" && 
  46:     field.InternalName != "FileLeafRef" &&
  47:     item[field.InternalName] != null)
  48:    {
  49:     info.FieldData.Add(field.InternalName, item[field.InternalName].ToString());
  50:    }
  51:   }
  52:  }
  53:  return itemData;
  54: }
  55:  
  56: /// <summary>
  57: /// Gets the item data from XML.
  58: /// </summary>
  59: /// <param name="itemData">The item data.</param>
  60: /// <param name="manifestPath">The manifest path.</param>
  61: private static void SaveItemData(List<ItemInfo> itemData, string manifestPath)
  62: {
  63:  if (string.IsNullOrEmpty(manifestPath))
  64:   throw new ArgumentNullException("manifest", "No directory was specified for the manifest.");
  65:  
  66:  if (!Directory.Exists(manifestPath))
  67:   Directory.CreateDirectory(manifestPath);
  68:  
  69:  string dataPath = Path.Combine(manifestPath, "Data");
  70:  
  71:  StringBuilder sb = new StringBuilder();
  72:  
  73:  XmlTextWriter xmlWriter = new XmlTextWriter(new StringWriter(sb));
  74:  xmlWriter.Formatting = Formatting.Indented;
  75:  
  76:  xmlWriter.WriteStartElement("Items");
  77:  
  78:  foreach (ItemInfo info in itemData)
  79:  {
  80:   xmlWriter.WriteStartElement("Item");
  81:  
  82:   if (info.File != null)
  83:   {
  84:    string folder = Path.Combine(dataPath, info.FolderUrl.Trim('\\', '/')).Replace("/", "\\");
  85:    if (!Directory.Exists(folder))
  86:     Directory.CreateDirectory(folder);
  87:  
  88:    xmlWriter.WriteAttributeString("File", Path.Combine(folder, info.File.Name));
  89:    xmlWriter.WriteAttributeString("Author", info.File.Author.LoginName);
  90:    xmlWriter.WriteAttributeString("CreatedDate", info.File.CreatedDate.ToString());
  91:    File.WriteAllBytes(Path.Combine(folder, info.File.Name), info.File.File);
  92:   }
  93:   xmlWriter.WriteAttributeString("LeafName", info.Title);
  94:   xmlWriter.WriteAttributeString("FolderUrl", info.FolderUrl);
  95:     
  96:   xmlWriter.WriteStartElement("Fields");
  97:   foreach (string key in info.FieldData.Keys)
  98:   {
  99:    xmlWriter.WriteStartElement("Field");
 100:    xmlWriter.WriteAttributeString("Name", key);
 101:    xmlWriter.WriteString(info.FieldData[key]);
 102:    xmlWriter.WriteEndElement(); // Field
 103:   }
 104:   xmlWriter.WriteEndElement(); // Fields
 105:  
 106:   xmlWriter.WriteStartElement("Attachments");
 107:   foreach (FileDetails file in info.Attachments)
 108:   {
 109:    string folder = Path.Combine(Path.Combine(dataPath, info.FolderUrl.Trim('\\', '/')).Replace("/", "\\"), "item_" + info.ID);
 110:    if (!Directory.Exists(folder))
 111:     Directory.CreateDirectory(folder);
 112:    
 113:    xmlWriter.WriteElementString("Attachment", Path.Combine(folder, file.Name));
 114:  
 115:    File.WriteAllBytes(Path.Combine(folder, file.Name), file.File);
 116:   }
 117:   xmlWriter.WriteEndElement(); // Attachments
 118:  
 119:   xmlWriter.WriteEndElement(); // Item
 120:  }
 121:  
 122:  xmlWriter.WriteEndElement();
 123:  xmlWriter.Flush();
 124:  
 125:  File.WriteAllText(Path.Combine(manifestPath, "Manifest.xml"), sb.ToString());
 126: }
 127:  
 128: #region Private Classes
 129:  
 130: private class FileDetails
 131: {
 132:  public byte[] File = null;
 133:  public string Name = null;
 134:  public SPUser Author = null;
 135:  public DateTime CreatedDate = DateTime.Now;
 136:  public FileDetails(byte[] file, string name, SPUser author, DateTime createdDate)
 137:  {
 138:   File = file;
 139:   Name = name;
 140:   Author = author;
 141:   CreatedDate = createdDate;
 142:  }
 143: }
 144: private class ItemInfo
 145: {
 146:  public FileDetails File = null;
 147:  public string FolderUrl = null;
 148:  public List<FileDetails> Attachments = new List<FileDetails>();
 149:  public Dictionary<string, string> FieldData = new Dictionary<string, string>();
 150:  public int ID = -1;
 151:  public string Title = null;
 152: }
 153: #endregion

The syntax of the command can be seen below:

C:\>stsadm -help gl-exportlistitem2

stsadm -o gl-exportlistitem2

Exports list items to disk (exported results can be used with addlistitem).

Parameters:
        -url <list view url to export from>
        -path <export path>
        [-id <list item ID (separate multiple items with a comma)>]
Here's an example of how to do export list items:
stsadm -o gl-exportlistitem2 -url "http://intranet/documents/forms/allitems.aspx" -path "c:\documents"
Note that a "Data" folder will be created under the path specified - all files will be put in this folder and the folder structure will mirror that of the list. The Manifest.xml file will be in the root of the folder specified. Attachments will be stored in sub-folders using the name "item_{ID}" where {ID} is the item ID. Once exported you could then use the gl-addlistitem command to import these items to another list:
stsadm -o gl-addlistitem -url "http://intranet/documents2/forms/allitems.aspx" -datafile "c:\documents\manifest.xml" -publish
Update 1/31/2008: I've modified this command so that it now also supports exporting web part pages. The resultant exported manifest file can be used in conjunction with the gl-addlistitem command so that web part pages can be properly imported using that command.

13Sep/0730

Re-Ghosting Pages

After running a test upgrade I discovered that one page particular was showing up as un-ghosted (or customized) despite my setting the option to reset all pages to the site definition when I ran the upgrade. I attempted to use the browser to reset the page (in this case http://intranet/sitedirectory/lists/sites/summary.aspx) but that had no affect.

I decided that I needed to get more information about how the un-ghosting process works. The first thing I needed to do was see if there were any other pages with the same problem. To do this I created a command called gl-enumunghostedfiles. I know there are versions of the same command out there already but I found I needed something a bit more capable so that I could search an entire site collection and not just a single web.

After creating this command (detailed below) I was surprised to see that this summary.aspx page was not showing up as un-ghosted at all. When looking for an un-ghosted file you typically just check the CustomizedPageStatus property of an SPFile object. If this returns back as Customized (so much better than saying "un-ghosted" - not sure why this term came up and why I'm proliferating it :) ) then the page is un-ghosted. If you look internally at how this property is evaluated you'll see that the code is checking for the presense of a property in the Properties collection called "vti_setuppath" - if this property is not null or empty then it checks for another property called "vti_hasdefaultcontent" and if it either doesn't find this or it's set to false then it returns back a value of Customized. You can see this in the code below:

   1: public SPCustomizedPageStatus CustomizedPageStatus
   2: {
   3:     get
   4:     {
   5:         if (!string.IsNullOrEmpty(this.SetupPath))
   6:         {
   7:             bool flag = false;
   8:             try
   9:             {
  10:                 object obj2 = this.Properties["vti_hasdefaultcontent"];
  11:                 if (obj2 != null)
  12:                 {
  13:                     flag = bool.Parse((string) obj2);
  14:                 }
  15:             }
  16:             catch (FormatException)
  17:             {
  18:             }
  19:             if (flag)
  20:             {
  21:                 return SPCustomizedPageStatus.Uncustomized;
  22:             }
  23:             return SPCustomizedPageStatus.Customized;
  24:         }
  25:         return SPCustomizedPageStatus.None;
  26:     }
  27: }

What I found when I looked closer at this page that I knew (based on the shear appearance of the page) was un-ghosted was that the "vti_hasdefaultcontent" property was set to "true" and the "vti_setuppath" property was set to the old 2003 template path. I spent many hours trying to figure out how to re-ghost this page and ended up ultimately unsuccessful. If anyone has any thoughts on this I'd love to hear them (I've tried updating the SetupPath and SetupPathVerion fields in the AllDocs table to point the file to the new template but that just resulted in an unknown error when loading the page - changing the "vti_hasdefaultcontent" property manually also didn't work as the value would not persist and I couldn't find where it's stored in the DB and changing this value in memory would allow the SPRequest.RevertContentStreams() method to be called but that would just throw a file not found exception as it is unable to locate the template file despite copying the file to various suspect locations).

As a result of my efforts though I do have a fairly robust command to re-ghost pages which does seem to work with another issue I encountered. I found that when I imported a list from another site the pages (views) for the list were showing up as un-ghosted but when I tried to use the RevertContentStream method of the SPFile object it had no affect.

After messing around with it for a while I discovered that if I called the internal SPRequest.RevertContentStreams() method directly and did not follow that call up with the call to SPRequest.UpdateFileOrFolderProperties() as the SPFile.RevertContentStream() method does then I can get the file to successfully be re-ghosted. I've got an email out to Microsoft to see if they can explain why this is so but in the mean-time it seems to work. The two commands that I created are detailed further below.

1. gl-enumunghostedfiles

The code for this command is pretty simple - I've basically just got two recursive methods, one for the web sites and another for folders. I allowed a parameter to be passed in to determine whether the code should recurse sub webs or not. The code is shown below:

   1: public class EnumUnGhostedFiles : SPOperation
   2: {
   3:     /// <summary>
   4:     /// Initializes a new instance of the <see cref="EnumUnGhostedFiles"/> class.
   5:     /// </summary>
   6:     public EnumUnGhostedFiles()
   7:     {
   8:         SPParamCollection parameters = new SPParamCollection();
   9:         parameters.Add(new SPParam("url", "url", true, null, new SPUrlValidator(), "Please specify the site url."));
  10:         parameters.Add(new SPParam("recursesubwebs", "recurse", false, null, null));
  11:         Init(parameters, "\r\n\r\nReturns a list of all unghosted (customized) files for a web.\r\n\r\nParameters:\r\n\t-url <web site url>\r\n\t[-recursesubwebs]");
  12:     }
  13:  
  14:     #region ISPStsadmCommand Members
  15:  
  16:     /// <summary>
  17:     /// Gets the help message.
  18:     /// </summary>
  19:     /// <param name="command">The command.</param>
  20:     /// <returns></returns>
  21:     public override string GetHelpMessage(string command)
  22:     {
  23:         return HelpMessage;
  24:     }
  25:  
  26:     /// <summary>
  27:     /// Runs the specified command.
  28:     /// </summary>
  29:     /// <param name="command">The command.</param>
  30:     /// <param name="keyValues">The key values.</param>
  31:     /// <param name="output">The output.</param>
  32:     /// <returns></returns>
  33:     public override int Run(string command, System.Collections.Specialized.StringDictionary keyValues, out string output)
  34:     {
  35:         output = string.Empty;
  36:  
  37:         InitParameters(keyValues);
  38:  
  39:         string url = Params["url"].Value;
  40:         bool recurse = Params["recursesubwebs"].UserTypedIn;
  41:         List<string> unghostedFiles = new List<string>();
  42:  
  43:         using (SPSite site = new SPSite(url))
  44:         {
  45:             using (SPWeb web = site.OpenWeb())
  46:             {
  47:                 if (recurse)
  48:                 {
  49:                     RecurseSubWebs(web, ref unghostedFiles);
  50:                 }
  51:                 else 
  52:                     CheckFoldersForUnghostedFiles(web.RootFolder, ref unghostedFiles);
  53:             }
  54:         }
  55:  
  56:         if (unghostedFiles.Count == 0)
  57:         {
  58:             output += "There are no unghosted (customized) files on the current web.\r\n";
  59:         }
  60:         else
  61:         {
  62:             output += "The following files are unghosted:";
  63:  
  64:             foreach (string fileName in unghostedFiles)
  65:             {
  66:                 output += "\r\n\t" + fileName;
  67:             }
  68:         }
  69:  
  70:         return 1;
  71:     }
  72:  
  73:     #endregion
  74:  
  75:     /// <summary>
  76:     /// Recurses the sub webs.
  77:     /// </summary>
  78:     /// <param name="web">The web.</param>
  79:     /// <param name="unghostedFiles">The unghosted files.</param>
  80:     private static void RecurseSubWebs(SPWeb web, ref List<string>unghostedFiles)
  81:     {
  82:         foreach (SPWeb subweb in web.Webs)
  83:         {
  84:             try
  85:             {
  86:                 RecurseSubWebs(subweb, ref unghostedFiles);
  87:             }
  88:             finally
  89:             {
  90:                 subweb.Dispose();
  91:             }
  92:         }
  93:         CheckFoldersForUnghostedFiles(web.RootFolder, ref unghostedFiles);
  94:     }
  95:  
  96:     /// <summary>
  97:     /// Checks the folders for unghosted files.
  98:     /// </summary>
  99:     /// <param name="folder">The folder.</param>
 100:     /// <param name="unghostedFiles">The unghosted files.</param>
 101:     private static void CheckFoldersForUnghostedFiles(SPFolder folder, ref List<string> unghostedFiles)
 102:     {
 103:         foreach (SPFolder sub in folder.SubFolders)
 104:         {
 105:             CheckFoldersForUnghostedFiles(sub, ref unghostedFiles);
 106:         }
 107:  
 108:         foreach (SPFile file in folder.Files)
 109:         {
 110:             if (file.CustomizedPageStatus == SPCustomizedPageStatus.Customized)
 111:             {
 112:                 if (!unghostedFiles.Contains(file.ServerRelativeUrl))
 113:                     unghostedFiles.Add(file.ServerRelativeUrl);
 114:             }
 115:         }
 116:     }
 117: }

The syntax of the command can be seen below:

C:\>stsadm -help gl-enumunghostedfiles

stsadm -o gl-enumunghostedfiles

Returns a list of all unghosted (customized) files for a web.

Parameters:
        -url <web site url>
        [-recursesubwebs]

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-enumunghostedfiles WSS v3, MOSS 2007 Released: 9/13/2007

Parameter Name Short Form Required Description Example Usage
url   Yes URL to analyze. -url http://intranet/
recursesubwebs recurse No If not specified then only the single web will be considered.  To recurse the web and all it’s sub-webs pass in this parameter. -recursesubwebs

-recurse

Here’s an example of how to return the un-ghosted files for a root site collection and all sub-webs:

stsadm –o gl-enumunghostedfiles -url "http://intranet/" -recursesubwebs

You can see a sample of what running the above command will produce - your results will most likely be very different:

C:\>stsadm –o gl-enumunghostedfiles -url "http://intranet/" -recursesubwebs
The following files are unghosted:
        /News/Pages/Default.aspx
        /Reports/Pages/default.aspx
        /SearchCenter/Pages/people.aspx
        /SearchCenter/Pages/default.aspx
        /SearchCenter/Pages/peopleresults.aspx
        /SearchCenter/Pages/results.aspx
        /SearchCenter/Pages/advanced.aspx
        /SiteDirectory/Pages/category.aspx
        /SiteDirectory/Pages/sitemap.aspx
        /Pages/Default.aspx
        /FormServerTemplates/Forms/InfoPath Form Template/template.doc
        /Variation Labels/NewForm.aspx
        /Variation Labels/EditForm.aspx
        /Variation Labels/AllItems.aspx
        /Variation Labels/DispForm.aspx
        /_catalogs/masterpage/VariationRootPageLayout.aspx
        /_catalogs/wp/siteFramer.dwp
        /_catalogs/wp/IViewWebPart.dwp
        /_catalogs/wp/IndicatorWebPart.dwp
        /_catalogs/wp/BusinessDataFilter.dwp
        /_catalogs/wp/KpiListWebPart.dwp
        /_catalogs/wp/SummaryLink.webpart
        /_catalogs/wp/ContentQuery.webpart
        /_catalogs/wp/ThisWeekInPictures.DWP
        /_catalogs/wp/SearchBestBets.webpart
        /_catalogs/wp/CategoryWebPart.webpart
        /_catalogs/wp/QueryStringFilter.webpart
        /_catalogs/wp/SpListFilter.dwp
        /_catalogs/wp/WSRPConsumerWebPart.dwp
        /_catalogs/wp/searchpaging.dwp
        /_catalogs/wp/contactwp.dwp
        /_catalogs/wp/searchstats.dwp
        /_catalogs/wp/UserContextFilter.webpart
        /_catalogs/wp/owacontacts.dwp
        /_catalogs/wp/SearchCoreResults.webpart
        /_catalogs/wp/BusinessDataActionsWebPart.dwp
        /_catalogs/wp/owainbox.dwp
        /_catalogs/wp/FilterActions.dwp
        /_catalogs/wp/AdvancedSearchBox.dwp
        /_catalogs/wp/BusinessDataAssociationWebPart.webpart
        /_catalogs/wp/RssViewer.webpart
        /_catalogs/wp/owatasks.dwp
        /_catalogs/wp/SearchHighConfidence.webpart
        /_catalogs/wp/PeopleSearchBox.dwp
        /_catalogs/wp/SearchActionLinks.webpart
        /_catalogs/wp/PageContextFilter.webpart
        /_catalogs/wp/owacalendar.dwp
        /_catalogs/wp/AuthoredListFilter.webpart
        /_catalogs/wp/searchsummary.dwp
        /_catalogs/wp/CategoryResultsWebPart.webpart
        /_catalogs/wp/owa.dwp
        /_catalogs/wp/OlapFilter.dwp
        /_catalogs/wp/BusinessDataDetailsWebPart.webpart
        /_catalogs/wp/TableOfContents.webpart
        /_catalogs/wp/BusinessDataListWebPart.webpart
        /_catalogs/wp/TasksAndTools.webpart
        /_catalogs/wp/Microsoft.Office.Excel.WebUI.dwp
        /_catalogs/wp/DateFilter.dwp
        /_catalogs/wp/TextFilter.dwp
        /_catalogs/wp/SearchBox.dwp
        /_catalogs/wp/BusinessDataItemBuilder.dwp
        /_catalogs/wp/TopSitesWebPart.webpart
        /_catalogs/wp/PeopleSearchCoreResults.webpart

2. gl-reghostfile

This command takes what should be a very simple call to SPFile.RevertContentStream() and attempts to handle those odd cases that I outlined above. Therefore the code is a bit of a mess and frankly nothing I'm proud of (mainly because I'm pissed I wasn't able to solve the problem). The code, which uses some reflection in order to utilize some internal objects, is shown below (sorry about the poor formatting - this blog template is less than ideal for code samples):

   1: /// <summary>
   2: /// Runs the specified command.
   3: /// </summary>
   4: /// <param name="command">The command.</param>
   5: /// <param name="keyValues">The key values.</param>
   6: /// <param name="output">The output.</param>
   7: /// <returns></returns>
   8: public override int Execute(string command, System.Collections.Specialized.StringDictionary keyValues, out string output)
   9: {
  10:     output = string.Empty;
  11:     Verbose = true;
  12:     
  13:  
  14:     string url = Params["url"].Value;
  15:     bool force = Params["force"].UserTypedIn;
  16:     string scope = Params["scope"].Value.ToLowerInvariant();
  17:     bool haltOnError = Params["haltonerror"].UserTypedIn;
  18:  
  19:     switch (scope)
  20:     {
  21:         case "file":
  22:             using (SPSite site = new SPSite(url))
  23:             using (SPWeb web = site.OpenWeb())
  24:             {
  25:                 SPFile file = web.GetFile(url);
  26:                 if (file == null)
  27:                 {
  28:                     throw new FileNotFoundException(string.Format("File '{0}' not found.", url), url);
  29:                 }
  30:  
  31:                 Reghost(site, web, file, force, haltOnError);
  32:             }
  33:             break;
  34:         case "list":
  35:             using (SPSite site = new SPSite(url))
  36:             using (SPWeb web = site.OpenWeb())
  37:             {
  38:                 SPList list = Utilities.GetListFromViewUrl(web, url);
  39:                 ReghostFilesInList(site, web, list, force, haltOnError);
  40:             }
  41:             break;
  42:         case "web":
  43:             bool recurseWebs = Params["recursewebs"].UserTypedIn;
  44:             using (SPSite site = new SPSite(url))
  45:             using (SPWeb web = site.AllWebs[Utilities.GetServerRelUrlFromFullUrl(url)])
  46:             {
  47:                 ReghostFilesInWeb(site, web, recurseWebs, force, haltOnError);
  48:             }
  49:             break;
  50:         case "site":
  51:             using (SPSite site = new SPSite(url))
  52:             {
  53:                 ReghostFilesInSite(site, force, haltOnError);
  54:             }
  55:             break;
  56:         case "webapplication":
  57:             SPWebApplication webApp = SPWebApplication.Lookup(new Uri(url));
  58:             Log("Progress: Analyzing files in web application '{0}'.", url);
  59:  
  60:             foreach (SPSite site in webApp.Sites)
  61:             {
  62:                 try
  63:                 {
  64:                     ReghostFilesInSite(site, force, haltOnError);
  65:                 }
  66:                 finally
  67:                 {
  68:                     site.Dispose();
  69:                 }
  70:             }
  71:             break;
  72:             
  73:     }
  74:     return OUTPUT_SUCCESS;
  75: }
  76:  
  77: #endregion
  78:  
  79: /// <summary>
  80: /// Reghosts the files in site.
  81: /// </summary>
  82: /// <param name="site">The site.</param>
  83: /// <param name="force">if set to <c>true</c> [force].</param>
  84: /// <param name="throwOnError">if set to <c>true</c> [throw on error].</param>
  85: public static void ReghostFilesInSite(SPSite site, bool force, bool throwOnError)
  86: {
  87:     Log("Progress: Analyzing files in site collection '{0}'.", site.Url);
  88:     foreach (SPWeb web in site.AllWebs)
  89:     {
  90:         try
  91:         {
  92:             ReghostFilesInWeb(site, web, false, force, throwOnError);
  93:         }
  94:         finally
  95:         {
  96:             web.Dispose();
  97:         }
  98:     }
  99: }
 100:  
 101: /// <summary>
 102: /// Reghosts the files in web.
 103: /// </summary>
 104: /// <param name="site">The site.</param>
 105: /// <param name="web">The web.</param>
 106: /// <param name="recurseWebs">if set to <c>true</c> [recurse webs].</param>
 107: /// <param name="force">if set to <c>true</c> [force].</param>
 108: /// <param name="throwOnError">if set to <c>true</c> [throw on error].</param>
 109: public static void ReghostFilesInWeb(SPSite site, SPWeb web, bool recurseWebs, bool force, bool throwOnError)
 110: {
 111:     Log("Progress: Analyzing files in web '{0}'.", web.Url);
 112:     foreach (SPFile file in web.Files)
 113:     {
 114:         if (file.CustomizedPageStatus != SPCustomizedPageStatus.Customized && !force)
 115:             continue;
 116:  
 117:         Reghost(site, web, file, force, throwOnError);    
 118:     }
 119:     foreach (SPList list in web.Lists)
 120:     {
 121:         ReghostFilesInList(site, web, list, force, throwOnError);
 122:     }
 123:     
 124:     if (recurseWebs)
 125:     {
 126:         foreach (SPWeb childWeb in web.Webs)
 127:         {
 128:             try
 129:             {
 130:                 ReghostFilesInWeb(site, childWeb, true, force, throwOnError);
 131:             }
 132:             finally
 133:             {
 134:                 childWeb.Dispose();
 135:             }
 136:         }
 137:     }
 138: }
 139:  
 140: /// <summary>
 141: /// Reghosts the files in list.
 142: /// </summary>
 143: /// <param name="site">The site.</param>
 144: /// <param name="web">The web.</param>
 145: /// <param name="list">The list.</param>
 146: /// <param name="force">if set to <c>true</c> [force].</param>
 147: /// <param name="throwOnError">if set to <c>true</c> [throw on error].</param>
 148: public static void ReghostFilesInList(SPSite site, SPWeb web, SPList list, bool force, bool throwOnError)
 149: {
 150:     if (list.BaseType != SPBaseType.DocumentLibrary)
 151:         return;
 152:  
 153:     Log("Progress: Analyzing files in list '{0}'.", list.RootFolder.ServerRelativeUrl);
 154:  
 155:     foreach (SPListItem item in list.Items)
 156:     {
 157:         if (item.File == null)
 158:             continue;
 159:  
 160:         Reghost(site, web, item.File, force, throwOnError);
 161:     }
 162: }
 163:  
 164: /// <summary>
 165: /// Reghosts the specified file.
 166: /// </summary>
 167: /// <param name="site">The site.</param>
 168: /// <param name="web">The web.</param>
 169: /// <param name="file">The file.</param>
 170: /// <param name="force">if set to <c>true</c> [force].</param>
 171: /// <param name="throwOnError">if set to <c>true</c> [throw on error].</param>
 172: public static void Reghost(SPSite site, SPWeb web, SPFile file, bool force, bool throwOnError)
 173: {
 174:     try
 175:     {
 176:         string fileUrl = site.MakeFullUrl(file.ServerRelativeUrl);
 177:         if (file.CustomizedPageStatus != SPCustomizedPageStatus.Customized && !force)
 178:         {
 179:             Log("Progress: " + file.ServerRelativeUrl + " was not unghosted (customized).");
 180:             return;
 181:         }
 182:         if (file.CustomizedPageStatus != SPCustomizedPageStatus.Customized && force)
 183:         {
 184:             if (!string.IsNullOrEmpty((string)file.Properties["vti_setuppath"]))
 185:             {
 186:                 file.Properties["vti_hasdefaultcontent"] = "false";
 187:  
 188:                 string setupPath = (string)file.Properties["vti_setuppath"];
 189:                 string rootPath = SPUtility.GetGenericSetupPath("Template");
 190:  
 191:                 if (!File.Exists(Path.Combine(rootPath, setupPath)))
 192:                 {
 193:                     string message = "The template file (" + Path.Combine(rootPath, setupPath) +
 194:                                      ") does not exist so re-ghosting (uncustomizing) will not be possible.";
 195:  
 196:                     // something's wrong with the setup path - lets see if we can fix it
 197:                     // Try and remove a leading locale if present
 198:                     setupPath = "SiteTemplates\\" + setupPath.Substring(5);
 199:                     if (File.Exists(Path.Combine(rootPath, setupPath)))
 200:                     {
 201:                         message += "  It appears that a possible template match does exist at \"" +
 202:                                    Path.Combine(rootPath, setupPath) +
 203:                                    "\" however this tool currently is not able to handle pointing the file to the correct template path.  This scenario is most likely due to an upgrade from SPS 2003.";
 204:  
 205:                         // We found a matching file so reset the property and update the file.
 206:                         // ---  I wish this would work but it simply doesn't - something is preventing the
 207:                         //      update from occuring.  Manipulating the database directly results in a 404
 208:                         //      when attempting to load the "fixed" page so there's gotta be something beyond
 209:                         //      just updating the setuppath property.
 210:                         //file.Properties["vti_setuppath"] = setupPath;
 211:                         //file.Update();
 212:                     }
 213:                     throw new FileNotFoundException(message, setupPath);
 214:                 }
 215:             }
 216:         }
 217:         Log("Progress: Re-ghosting (uncustomizing) '{0}'", fileUrl);
 218:         file.RevertContentStream();
 219:  
 220:         file = web.GetFile(fileUrl);
 221:         if (file.CustomizedPageStatus == SPCustomizedPageStatus.Customized)
 222:         {
 223:             // Still unsuccessful so take measures further
 224:             if (force)
 225:             {
 226:                 object request = Utilities.GetSPRequestObject(web);
 227:  
 228:                 // I found some cases where calling this directly was the only way to force the re-ghosting of the file.
 229:                 // I think the trick is that it's not updating the file properties after doing the revert (the
 230:                 // RevertContentStream method will call SPRequest.UpdateFileOrFolderProperties() immediately after the 
 231:                 // RevertContentStreams call but ommitting the update call seems to make a difference.
 232:                 Utilities.ExecuteMethod(request, "RevertContentStreams",
 233:                                         new[] { typeof(string), typeof(string), typeof(bool) },
 234:                                         new object[] { web.Url, file.Url, file.CheckOutStatus != SPFile.SPCheckOutStatus.None });
 235:  
 236:  
 237:                 Utilities.ExecuteMethod(file, "DirtyThisFileObject", new Type[] { }, new object[] { });
 238:  
 239:                 file = web.GetFile(fileUrl);
 240:  
 241:                 if (file.CustomizedPageStatus == SPCustomizedPageStatus.Customized)
 242:                 {
 243:                     throw new SPException("Unable to re-ghost (uncustomize) file " + file.ServerRelativeUrl);
 244:                 }
 245:                 Log("Progress: " + file.ServerRelativeUrl + " was re-ghosted (uncustomized)!");
 246:                 return;
 247:             }
 248:             throw new SPException("Unable to re-ghost (uncustomize) file " + file.ServerRelativeUrl);
 249:         }
 250:         Log("Progress: " + file.ServerRelativeUrl + " was re-ghosted (uncustomized)!");
 251:     }
 252:     catch (Exception ex)
 253:     {
 254:         if (throwOnError)
 255:         {
 256:             Log("ERROR:");
 257:             throw;
 258:         }
 259:         Log("ERROR: {0}", ex.Message);
 260:     }
 261:  
 262: }

The syntax of the command can be seen below:

C:\>stsadm -help gl-reghostfile

stsadm -o gl-reghostfile


Reghosts a file (use force to override CustomizedPageStatus check).

Parameters:
        -url <url to analyze>
        [-force]
        [-scope <WebApplication | Site | Web | List | File>]
        [-recursewebs (applies to Web scope only)]
        [-haltonerror]

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-reghostfile WSS v3, MOSS 2007 Released: 9/13/2007
Updated: 12/14/2008 

Parameter Name Short Form Required Description Example Usage
url   Yes URL to analyze.  If scope is “File” then URL must point to a valid file within a Web.  If scope is “List” then URL must point to a valid List within a Web. -url "http://intranet/sitedirectory/lists/sites/summary.aspx"
force   No Attempts to force the reghosting of file(s) using internal API method calls (via reflection). -force
scope   No (defaults to File) The scope to look at when reghosting files.  Valid values are “WebApplication”, “Site”, “Web”, “List”, or “File”. -scope file
recursewebs recurse No Applies to “Web” scope only.  If a scope of “Web” is not specified then only the single web will be considered.  To recurse the web and all it’s sub-webs pass in this parameter. -recursewebs

-recurse
haltonerror halt No If an error occurs then stop processing other files within the specified scope. -haltonerror

-halt

Here’s an example of how to force the reghosting of a file:

stsadm –o gl-reghostfile -url "http://intranet/sitedirectory/lists/sites/summary.aspx" –scope file -force

If I'm able to get any answers to the issues that remain unsolved for me I'll be sure to post them here (especially if I'm able to fix the issues).