SharePoint 2010 Export/Import

Overview

SharePoint 2010 has two ways of backing up / exporting data, one is backing up the data and the other is exporting the data. To backup the data, you can use either the STSADM operation "stsadm -o backup" or the PowerShell module "Backup-SPSite". Backup is usually done as part of a disaster recover plan, and when your intention is to restore the system back to its original state in case of problems. The reason is that the backup tool produces a file that is very similar to a database backup file, and this file is very difficult to make changes to and requires a very similar system for restore.

On the other hand, SharePoint 2010 provides another excellent utility for exporting data, you can use either the STSADM operation "stsadm -o export" or the PowerShell module "Export-SPWeb". Export is usually done when you need to migrate or move the data over to a different SharePoint 2010 platform. The file produced by the export utility is in essence a compressed CAB package file with a different file extension. Normally CAB files have ".cab" as a file extension, while SharePoint 2010 produces either ".cmp" or ".wsp" files. The exported file can contain Site Collections, Sites, Pages, Lists or Libraries and other site content.

After you have exported a site or site collection, you’d think you should be able to import the file easily to another SharePoint platform, but ... sometimes the site you export contains dependencies that are custom to the platform you exported it from. Let’’s say the site uses custom solutions and features and even custom masterpages that the destined platform does not have. Well, you need to remove these dependencies somehow before you import it somewhere else. Microsoft does provide a solution ... remember I mentioned previously that the exported file is a compressed CAB package, so it can be opened and explored like any other compressed file. The files inside are usually XML files, which makes editing them even easier, except for the data/content files that are usually binary/compressed files.

After extracting the export file, and editing the XML definition files, you need to re-package the files in to a final CAB (with ".wsp" or ".cmp" extension) file, to be able to do this we can use Microsoft's built-in Windows/DOS utility called MAKECAB.EXE. Also MAKECAB.EXE needs a definition file with a ".ddf" extension known as a Data Definition File or a DDF file that tells it how the CAB package will be compressed and which files it should contain.

So let’s start the procedure of extracting, modifying and re-packaging the file. PS! You can use this guide for changing the structure or content of any compressed Windows CAB package.

Extracting the Exported Package

First we’’ll start by extracting the exported package. You can use any valid Compression Utility to extract the file, my personal favorite is 7-Zip which is free and has a command-line edition. You can download the latest version from here or directly download version 9.20 from here. You can manually extract the file, or use the below PowerShell script:

  1. function ExtractExportFile()
  2. {
  3.     # Main variables.
  4.     [String] $compressApp = "C:\Path\To\Zip\7z.exe";
  5.     [String] $compressArgs = "x ""export_file.cmp"" -o""C:\Dir\export_dir"" * -r";
  6.  
  7.     Write-Host("");
  8.     Write-Host("");
  9.     Write-Host("Creating a temporary directory ... ");
  10.  
  11.     [void] [IO.Directory]::CreateDirectory($script:exportTempDir);
  12.  
  13.     Write-Host("");
  14.     Write-Host("Extracting files from the export file ... ");
  15.  
  16.     Start-Process -FilePath $compressApp -ArgumentList $compressArgs -Wait;
  17.  
  18.     # Run Garbage Collect to release unused memory.
  19.     [GC]::Collect();
  20. }

Removing Unwanted Features

Next, you need to open the "Requirements.xml" and "Manifest.xml" file, and then remove XML tags that define feature requirements which you don’t want. Start by finding the feature GUIDs in the "Requirements.xml" file. For example to remove the following features:

  • "Mobile Excel Web Access"
  • "Mobile PowerPoint Viewer"
  • "Mobile Word Viewer"
You need to search the "Requirements.xml" file for the following feature GUIDs:
  • "e995e28b-9ba8-4668-9933-cf5c146d7a9f"
  • "893627d9-b5ef-482d-a3bf-2a605175ac36"
  • "8dfaf93d-e23c-4471-9347-07368668ddaf"
And then you need to delete the entire "Requirement" node, the XML nodes can look something like this:

  1. <Requirement
  2.    Type="FeatureDefinition"
  3.    Id="e995e28b-9ba8-4668-9933-cf5c146d7a9f"
  4.    Name="MobileExcelWebAccess"
  5.    Data="14.0.0.0"
  6. />
  7.  
  8. <Requirement
  9.    Type="FeatureDefinition"
  10.    Id="893627d9-b5ef-482d-a3bf-2a605175ac36"
  11.    Name="MobilePowerPointViewer"
  12.    Data="14.0.0.0"
  13. />
  14.  
  15. <Requirement
  16.    Type="FeatureDefinition"
  17.    Id="8dfaf93d-e23c-4471-9347-07368668ddaf"
  18.    Name="MobileWordViewer"
  19.    Data="14.0.0.0"
  20. />

After you have found the feature GUIDs you need to search the entire "Manifest.xml" file for the same feature GUIDs. PS! This file can be really huge based on the size of the site you have exported it from, it defines the entire site structure, and can range from a megabyte to several hundred megabytes. When you find an "SPObject" node or a child node of "SPObject" that contains a reference to the feature GUIDs you want to remove, you need to delete the entire "SPObject" node including all child nodes from the XML file. An "SPObject" node with a child node can look something like this:

  1. <SPObject
  2.    Id="e995e28b-9ba8-4668-9933-cf5c146d7a9f"
  3.    ObjectType="SPFeature"
  4.    ParentId=""
  5.    ParentWebId="">
  6.  
  7.     <Feature
  8.        Id="e995e28b-9ba8-4668-9933-cf5c146d7a9f"
  9.        FeatureDefinitionName="MobileExcelWebAccess"
  10.        Version="14.0.0.0"
  11.        IsUserSolutionFeature="false"
  12.    />
  13. </SPObject>

After removing feature references, you also need to remove references to custom SharePoint master pages, if you know the names of the customer master pages you can just search for them directly, otherwise you can just search the entire "Manifest.xml" file for ".master" entries and remove the entire "SPObject" node including all child nodes if it or any of its child nodes contain references to any non-standard SharePoint 2010 master page or any master page that is not installed on the destination platform. Here’s an example of a custom master page reference node in "Manifest.xml":

  1. <SPObject
  2.    Id="70d44240-c1f4-4cfb-a64b-77ba4189c7e3"
  3.    ObjectType="SPFile"
  4.    ParentId="a565e2f9-0188-46c9-9bde-fefb61a55683"
  5.    ParentWebId="1f73f1f2-4e93-4ad0-af0f-ed29b8e57519"
  6.    ParentWebUrl="/sites/test_site"
  7.    Url="/sites/test_site/_catalogs/masterpage/CustomMasterPagev4.master">
  8.  
  9.     <File
  10.        Url="_catalogs/masterpage/CustomMasterPagev4.master"
  11.        Id="70d44240-c1f4-4cfb-a64b-77ba4189c7e3"
  12.        ParentWebId="1f73f1f2-4e93-4ad0-af0f-ed29b8e57519"
  13.        ParentWebUrl="/sites/test_site"
  14.        Name="CustomMasterPagev4.master"
  15.        ListId="42d7bded-4f85-4935-b179-851fa34be11c"
  16.        ParentId="a565e2f9-0188-46c9-9bde-fefb61a55683"
  17.        TimeCreated="2011-11-22T09:11:28"
  18.        TimeLastModified="2011-11-22T09:11:28"
  19.        Version="1.0"
  20.        IsGhosted="true"
  21.        SetupPath="Features\Layouts\MasterPages\CustomMasterPagev4.master"
  22.        SetupPathVersion="4"
  23.        SetupPathUser="1073741823"
  24.        FileValue="00000001.dat"
  25.    />
  26. </SPObject>

Data Definition File (DDF)

To be able to restore our export package to a valid CAB package, we need to first create a Data Definition File (DDF). The DDF file specifies how the CAB package should be created, like compression properties, which files should be included etc. A DDF file is a file with an extension of .ddf and formatted in a special way, the file can be for example "export.ddf", and the first part of the DDF file should include something like the following:

  1. .Option Explicit
  2.  
  3. ;Generate CAB package in the specified location.
  4. .Set Cabinet=ON
  5. .Set CabinetNameTemplate="export_file.cmp"
  6. .Set DiskDirectoryTemplate="C:\Dir"
  7.  
  8. ;Set Compression settings.
  9. ;Compression Levels: 15-21
  10. ;Compression Types: "MSZIP" or "LZX".
  11. .Set Compress=ON
  12. .Set CompressionMemory=21
  13. .Set CompressionType=LZX
  14.  
  15. ;Max files per CAB package.
  16. .Set CabinetFileCountThreshold=20000
  17.  
  18. ;Max bytes per contiguous set of compressed bytes.
  19. .Set FolderSizeThreshold=200000
  20.  
  21. ;Max size of the CAB package.
  22. ;Use either an integer for bytes or a variable value for standard CD or Floppy sizes.
  23. ;Valid variable values: "CDROM", "1.44M", "1.2M", "720K" or "360K".
  24. ;Example: 1GB => 1024000000 bytes
  25. .Set MaxDiskSize=1024000000
  26.  
  27. ;If all files go into the same folder then unique files
  28. ;can be set to ON to ensure no duplicate files exist.
  29. ;Otherwise set it to OFF when files go into various sub-folders,
  30. ;and some files can have the same name.
  31. .Set UniqueFiles=ON

The "CabinetNameTemplate" lets you name the final CAB package file, Normally it has a ".cab" extension but you can give it anything you want, and you should of course give it the same extension as the original package you exported, for example "export.cmp" or "export.wsp".

The "DiskDirectoryTemplate" specifies where to save the final CAB package file, you can assign a relative path or use an absolute path, an absolute path is recommended to avoid confusion as to where the file ultimately ends up.

The "CompressionType" lets you choose between "MSZip" and "LZX", "LZX" provide the best compression rate and is recommended.

The "CompressionMemory" specifies the compression level where the lowest is 15 and the highest is 21.

"CabinetFileCountThreshold" specifies the maximum number of files each CAB package can contain, and "MaxDiskSize" specifies the maximum file size of the CAB package.

You can fine-tune and optimize the compression by specifiyng the "FolderSizeThreshold", the higher the number of bytes the better the compression rate but slower seek time will be when reading or extracting the package, and the lower the number of bytes the lower the compression rate but faster seek time.

If "UniqueFiles" is set to "ON", the CAB package will refuse duplicate files, so it will fail if you have duplicate files in a directory structure, for example:

  • C:\Dir\Sub1\file.xml
  • C:\Dir\Sub2\file.xml

So, "UniqueFiles" should be set to "ON" only when all files are located in the same root directory, but if they are spread around in a directory structure and it’s possible that one or more files will have the same name.

Please refer to the official MAKECAB reference manual for more details and explanations on the various options.

The next part of the DDF file contains a list of files and directories to be included in the final CAB package:

  1. .Set DestinationDir="SubDir1"
  2.  
  3. .Set DestinationDir="SubDir1\SubDir2"
  4.  
  5. .Set DestinationDir="SubDir1\SubDir2\SubDir3"
  6. "C:\Dir\export_dir\SubDir1\SubDir2\SubDir3\file1.ext"
  7. "C:\Dir\export_dir\SubDir1\SubDir2\SubDir3\file2.ext"
  8.  
  9. .Set DestinationDir="SubDir1\SubDir2\SubDir4"
  10. "C:\Dir\export_dir\SubDir1\SubDir2\SubDir4\file3.ext"
  11. "C:\Dir\export_dir\SubDir1\SubDir2\SubDir4\file4.ext"

Or if all the files are in a single root directory without any sub-directory structure, you don’t need to specify the "DestinationDir" properties, like this:

  1. "C:\Dir\export_dir\file1.ext"
  2. "C:\Dir\export_dir\file2.ext"
  3. "C:\Dir\export_dir\file3.ext"
  4. "C:\Dir\export_dir\file4.ext"

An important thing, is that the "DestinationDir" MUST have a relative path and NOT an absolute path, while the file listing of the specific files on the other hand MUST have an absolute path. Also note, that to get a deep directory structure, even when most of the subdirectories should be empty and contain no files, they must be specified, for example below "SubDir2" is an empty sub-directory:

  1. "C:\Dir\export_dir\file.ext"
  2.  
  3. .Set DestinationDir="SubDir1"
  4. "C:\Dir\export_dir\SubDir1\file.ext"
  5.  
  6. .Set DestinationDir="SubDir1\SubDir2"
  7.  
  8. .Set DestinationDir="SubDir1\SubDir2\SubDir3"
  9. "C:\Dir\export_dir\SubDir1\SubDir2\SubDir3\file.ext"

The above will provide the following structure inside the CAB package:

file.ext
|_ SubDir1
  |_ SubDir2
  |  |_ SubDir3
  |    |_ file.ext
  |_file.ext

Think of the "DestinationDir" property as creating a directory, and the "C:\file.ext" as copying the specified file to the directory created by "DestinationDir".

If you are planning to create a CAB package with hundreds or thousands of files, and you don’t want to find and specify each line manually, you can use the below PowerShell script to create your initial DDF file, and then you can manually edit it and add/set the correct "DestinationDir" properties for WSP files with a deep directory structure:

  1. function CreateDDF()
  2. {
  3.     # Main variables.
  4.     $ddfPath = "C:\Dir\export.ddf";
  5.     $exportFiles = Get-ChildItem -Path "C:\Dir\export_file" -Recurse;
  6.  
  7.     Write-Host("");
  8.     Write-Host("Creating a DDF File ... ");
  9.  
  10.     Set-Content -Path $ddfPath -Value ".Option Explicit";
  11.     Add-Content -Path $ddfPath -Value "";
  12.     Add-Content -Path $ddfPath -Value ";Generate CAB package in the specified location.";
  13.     Add-Content -Path $ddfPath -Value ".Set Cabinet=ON";
  14.     Add-Content -Path $ddfPath -Value ".Set CabinetNameTemplate=""trimmed_export_file.cmp""";
  15.     Add-Content -Path $ddfPath -Value ".Set DiskDirectoryTemplate=""C:\Dir""";
  16.     Add-Content -Path $ddfPath -Value "";
  17.     Add-Content -Path $ddfPath -Value ";Set Compression settings.";
  18.     Add-Content -Path $ddfPath -Value ";Compression Levels: 15-21";
  19.     Add-Content -Path $ddfPath -Value ";Compression Types: ""MSZIP"" or ""LZX"".";
  20.     Add-Content -Path $ddfPath -Value ".set Compress=ON";
  21.     Add-Content -Path $ddfPath -Value ".Set CompressionMemory=21";
  22.     Add-Content -Path $ddfPath -Value ".Set CompressionType=LZX";
  23.     Add-Content -Path $ddfPath -Value "";
  24.     Add-Content -Path $ddfPath -Value ";Max files per CAB package.";
  25.     Add-Content -Path $ddfPath -Value ".Set CabinetFileCountThreshold=20000";
  26.     Add-Content -Path $ddfPath -Value "";
  27.     Add-Content -Path $ddfPath -Value ";Max bytes per contiguous set of compressed bytes.";
  28.     Add-Content -Path $ddfPath -Value ".Set FolderSizeThreshold=200000";
  29.     Add-Content -Path $ddfPath -Value "";
  30.     Add-Content -Path $ddfPath -Value ";Max size of the CAB package.";
  31.     Add-Content -Path $ddfPath -Value ";Use either an integer for bytes or a variable value for standard CD or Floppy sizes.";
  32.     Add-Content -Path $ddfPath -Value ";Valid variable values: ""CDROM"", ""1.44M"", ""1.2M"", ""720K"" or ""360K"".";
  33.     Add-Content -Path $ddfPath -Value ".Set MaxDiskSize=CDROM";
  34.     Add-Content -Path $ddfPath -Value "";
  35.     Add-Content -Path $ddfPath -Value ";If all files go into the same folder then unique files";
  36.     Add-Content -Path $ddfPath -Value ";can be set to ON to ensure no duplicate files exist.";
  37.     Add-Content -Path $ddfPath -Value ";Otherwise set it to OFF when files go into various sub-folders,";
  38.     Add-Content -Path $ddfPath -Value ";and some files can have the same name.";
  39.     Add-Content -Path $ddfPath -Value ".Set UniqueFiles=ON";
  40.     Add-Content -Path $ddfPath -Value "";
  41.  
  42.     # Parse the files/sub-directories in the export directory.
  43.     foreach ($file in $exportFiles)
  44.     {
  45.         # Add the file as an entry in the DDF file.
  46.         $fileName = [String]::Format("""{0}""", $file.FullName);
  47.  
  48.         Add-Content -Path $ddfPath -Value $fileName;
  49.     }
  50.  
  51.     # Run Garbage Collect to release unused memory.
  52.     [GC]::Collect();
  53. }

This will give you a list like:

  1. "C:\Dir\export_dir\file.ext"
  2. "C:\Dir\export_dir\SubDir1\file.ext"
  3. "C:\Dir\export_dir\SubDir1\SubDir2\SubDir3\file.ext"

Which you can quickly change to:

  1. "C:\Dir\export_dir\file1.ext"
  2.  
  3. .Set DestinationDir="SubDir1"
  4. "C:\Dir\export_dir\SubDir1\file2.ext"
  5.  
  6. .Set DestinationDir="SubDir1\SubDir2"
  7.  
  8. .Set DestinationDir="SubDir1\SubDir2\SubDir3"
  9. "C:\Dir\export_dir\SubDir1\SubDir2\SubDir3\file3.ext"

A quick note about the relative paths you assign the "DestinationDir" properties, the first file entry in the list in the DDF file sets the root path, and all other paths are relative to this.

The first file entry above is "C:\Dir\export_dir\file.ext" which will set the root path to "C:\Dir\export_dir". Now the next relative path "SubDir1" is relative to "C:\Dir\export_dir". Basically the relative paths are used to build a virtual directory inside the CAB package, and the absolute paths of the file entries is to be able to find the files on your local file system. This is important in the creation process, after you extract the package the next time, all files and directories will be extracted relative to the location where you extract the file, for example if extracted directly to "C:\New_Dir", then the extracted structure will be:

  1. "C:\New_Dir\file.ext"
  2. "C:\New_Dir\SubDir1
  3. "C:\New_Dir\SubDir1\file.ext"
  4. "C:\New_Dir\SubDir1\SubDir2"
  5. "C:\New_Dir\SubDir1\SubDir2\SubDir3
  6. "C:\New_Dir\SubDir1\SubDir2\SubDir3\file.ext"

MakeCAB

Finally when the DDF file is complete, we can use the built-in Windows/DOS "MAKECAB.EXE" command to generate the CAB package:

  1. function CreateCMP()
  2. {
  3.     # Main variables.
  4.     [String] $cabApp = "MAKECAB.EXE";
  5.     [String] $cabArgs = "/F "C:\Dir\export.ddf"";
  6.  
  7.     Write-Host("");
  8.     Write-Host("Creating a new trimmed SharePoint Export file ... ");
  9.  
  10.     Start-Process -FilePath $cabApp -ArgumentList $cabArgs -Wait;
  11.  
  12.     # Run Garbage Collect to release unused memory.
  13.     [GC]::Collect();
  14. }

This will produce a file with the filename specified in the "CabinetNameTemplate" property, and saved in the location specified in the "DiskDirectoryTemplate", for example:

  1. .Set CabinetNameTemplate="export_file.cmp"
  2. .Set DiskDirectoryTemplate="C:\Dir"

The above DDF definitions will produce the file "C:\Dir\eport_file.cmp".

PowerShell Script

Here is an example of a full PowerShell script, make sure you change the properties to what is needed for your environment:

  1. <#
  2. .SYNOPSIS
  3.     Removes unwanted features and solutions from a SP 2010 Export file.
  4. .DESCRIPTION
  5.     Extracts the CMP file exported from a SharePoint 2010 Site Collection,
  6.     Site or List, and removes all the unwanted features and solutions,
  7.     and then finally re-packages the export file that you can import into
  8.     a different SharePoint 2010 platform.
  9. .PARAMETER ddfFile
  10.     The "Data Definition File" also known as the DDF file,
  11.     it defines which files are to be inlcuded in the final CMP file,
  12.     and how it will be compressed, DDF files are always needed when
  13.     creating CAB packages.
  14. .PARAMETER exportDir
  15.     The path where the CMP file is located.
  16. .PARAMETER exportFileName
  17.     The filename of the CMP file without the file extension.
  18. .PARAMETER exportFileExtension
  19.     The file extension of the CMP file, usually it is "cmp".
  20. .EXAMPLE
  21.     .\StripFeaturesFromSP2010Export.ps1 -ddfFile "export.ddf" -exportDir "C:\Path\SubDir" -exportFileName "sp_export_site123_20120229" -exportFileExtension "cmp"
  22. .NOTES
  23.     Author: Abdellah Jammary
  24.     Date: February 29, 2012
  25. #>
  26.  
  27. Param(
  28.     [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String] $ddfFile,
  29.     [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String] $exportDir,
  30.     [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String] $exportFileName,
  31.     [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String] $exportFileExtension
  32. );
  33.  
  34. # Global variables.
  35. [String] $script:ddfPath = [String]::Format("{0}\{1}", $exportDir, $ddfFile);
  36. [String] $script:exportFile = [String]::Format("{0}\{1}.{2}", $exportDir, $exportFileName, $exportFileExtension);
  37. [String] $script:exportTempDir = [String]::Format("{0}\{1}", $exportDir, $exportFileName);
  38. [String] $script:manifestFile = [String]::Format("{0}\Manifest.xml", $script:exportTempDir);
  39. [String] $script:requirementsFile = [String]::Format("{0}\Requirements.xml", $script:exportTempDir);
  40.  
  41. # Global arrays.
  42. [String[]] $script:invalidFeatureIds = @(
  43.     "e995e28b-9ba8-4668-9933-cf5c146d7a9f",
  44.     "893627d9-b5ef-482d-a3bf-2a605175ac36",
  45.     "8dfaf93d-e23c-4471-9347-07368668ddaf"
  46. );
  47.  
  48. [String[]] $script:invalidMasterPages = @(
  49.     "Customv4.master",
  50.     "AnotherCustomv4.master"
  51. );
  52.  
  53. [String[]] $script:invalidMinimalMasterPages = @(
  54.     "CustomMinimal.master"
  55. );
  56.  
  57. function CleanTemporaryFiles()
  58. {
  59.     Write-Host("");
  60.     Write-Host("Cleaning up temporary files ... ");
  61.     Write-Host("");
  62.  
  63.     Remove-Item -Path $script:exportTempDir -Force -Recurse
  64.     Remove-Item -Path $script:ddfPath -Force
  65.     Remove-Item -Path "setup.inf" -Force
  66.     Remove-Item -Path "setup.rpt" -Force
  67.  
  68.     # Run Garbage Collect to release unused memory.
  69.     [GC]::Collect();
  70. }
  71.  
  72. function CreateCMP()
  73. {
  74.     # Main variables.
  75.     [String] $cabApp = "MAKECAB.EXE";
  76.     [String] $cabArgs = [String]::Format("/F {0}", $script:ddfPath);
  77.  
  78.     Write-Host("");
  79.     Write-Host("Creating a new trimmed SharePoint Export file ... ");
  80.  
  81.     Start-Process -FilePath $cabApp -ArgumentList $cabArgs -Wait;
  82.  
  83.     # Run Garbage Collect to release unused memory.
  84.     [GC]::Collect();
  85. }
  86.  
  87. function CreateDDF()
  88. {
  89.     # Main variables.
  90.     $cabinetNameTemplate = [String]::Format(".Set CabinetNameTemplate=""trimmed_{0}.{1}""", $exportFileName, $exportFileExtension);
  91.     $exportFiles = Get-ChildItem -Path $script:exportTempDir -Recurse;
  92.  
  93.     Write-Host("");
  94.     Write-Host("Creating a DDF File ... ");
  95.  
  96.     Set-Content -Path $script:ddfPath -Value ".Option Explicit";
  97.     Add-Content -Path $script:ddfPath -Value "";
  98.     Add-Content -Path $script:ddfPath -Value ";Generate CAB package in the specified location.";
  99.     Add-Content -Path $script:ddfPath -Value ".Set Cabinet=ON";
  100.     Add-Content -Path $script:ddfPath -Value $cabinetNameTemplate
  101.     Add-Content -Path $script:ddfPath -Value ".Set DiskDirectoryTemplate=""$exportDir""";
  102.     Add-Content -Path $script:ddfPath -Value "";
  103.     Add-Content -Path $script:ddfPath -Value ";Set Compression settings.";
  104.     Add-Content -Path $script:ddfPath -Value ";Compression Levels: 15-21";
  105.     Add-Content -Path $script:ddfPath -Value ";Compression Types: ""MSZIP"" or ""LZX"".";
  106.     Add-Content -Path $script:ddfPath -Value ".set Compress=ON";
  107.     Add-Content -Path $script:ddfPath -Value ".Set CompressionMemory=21";
  108.     Add-Content -Path $script:ddfPath -Value ".Set CompressionType=LZX";
  109.     Add-Content -Path $script:ddfPath -Value "";
  110.     Add-Content -Path $script:ddfPath -Value ";Max files per CAB package.";
  111.     Add-Content -Path $script:ddfPath -Value ".Set CabinetFileCountThreshold=20000";
  112.     Add-Content -Path $script:ddfPath -Value "";
  113.     Add-Content -Path $script:ddfPath -Value ";Max bytes per contiguous set of compressed bytes.";
  114.     Add-Content -Path $script:ddfPath -Value ".Set FolderSizeThreshold=200000";
  115.     Add-Content -Path $script:ddfPath -Value "";
  116.     Add-Content -Path $script:ddfPath -Value ";Max size of the CAB package.";
  117.     Add-Content -Path $script:ddfPath -Value ";Use either an integer for bytes or a variable value for standard CD or Floppy sizes.";
  118.     Add-Content -Path $script:ddfPath -Value ";Valid variable values: ""CDROM"", ""1.44M"", ""1.2M"", ""720K"" or ""360K"".";
  119.     Add-Content -Path $script:ddfPath -Value ".Set MaxDiskSize=CDROM";
  120.     Add-Content -Path $script:ddfPath -Value "";
  121.     Add-Content -Path $script:ddfPath -Value ";If all files go into the same folder then unique files";
  122.     Add-Content -Path $script:ddfPath -Value ";can be set to ON to ensure no duplicate files exist.";
  123.     Add-Content -Path $script:ddfPath -Value ";Otherwise set it to OFF when files go into various sub-folders,";
  124.     Add-Content -Path $script:ddfPath -Value ";and some files can have the same name.";
  125.     Add-Content -Path $script:ddfPath -Value ".Set UniqueFiles=ON";
  126.     Add-Content -Path $script:ddfPath -Value "";
  127.  
  128.     # Parse the files/sub-directories in the export directory.
  129.     foreach ($file in $exportFiles)
  130.     {
  131.         # Add the file as an entry in the DDF file.
  132.         $fileName = [String]::Format("""{0}""", $file.FullName);
  133.  
  134.         Add-Content -Path $script:ddfPath -Value $fileName;
  135.     }
  136.  
  137.     # Run Garbage Collect to release unused memory.
  138.     [GC]::Collect();
  139. }
  140.  
  141. function ExtractExportFile()
  142. {
  143.     # Main variables.
  144.     [String] $compressApp = "C:\Path\To\Zip\7z.exe";
  145.     [String] $compressArgs = [String]::Format("x ""{0}"" -o""{1}"" * -r", $script:exportFile, $script:exportTempDir);
  146.  
  147.     Write-Host("");
  148.     Write-Host("");
  149.     Write-Host("Creating a temporary directory ... ");
  150.  
  151.     [void] [IO.Directory]::CreateDirectory($script:exportTempDir);
  152.  
  153.     Write-Host("");
  154.     Write-Host("Extracting files from the export file ... ");
  155.  
  156.     Start-Process -FilePath $compressApp -ArgumentList $compressArgs -Wait;
  157.  
  158.     # Run Garbage Collect to release unused memory.
  159.     [GC]::Collect();
  160. }
  161.  
  162. function ResetMasterPageReferences()
  163. {
  164.     Write-Host("");
  165.     Write-Host("Loading the contents of the Manifest XML file ... ");
  166.     Write-Host("");
  167.    
  168.     # Load in the contents of the Manifest XML file.
  169.     [System.Xml.XmlDocument] $manifestXml = New-Object System.Xml.XmlDocument;
  170.     [Xml] $manifestXml.Load($script:manifestFile);
  171.    
  172.     # Add a <objects> Node-Alias for the <SPObjects xmlns="abc"> node so the XPATH parsing works.
  173.     [System.Xml.XmlNamespaceManager] $xmlNameSpaceManager = New-Object System.Xml.XmlNamespaceManager($manifestXml.Psbase.NameTable);
  174.     $xmlNameSpaceManager.AddNamespace("objects", $manifestXml.SPObjects.xmlns);
  175.    
  176.     # Get a collection/list of all the <SPObject> children nodes in <objects>.
  177.     $xmlNodeList = $manifestXml.SelectNodes("//objects:SPObject", $xmlNameSpaceManager);
  178.    
  179.     # Parse each child node and reset the MasterPage references.
  180.     foreach ($xmlNode in $xmlNodeList) {
  181.         foreach ($masterPage in $script:invalidMasterPages)
  182.         {
  183.             if ($xmlNode.getAttribute("ParentWebUrl").ToString().ToLower().Contains($masterPage.ToString().ToLower()))
  184.             {
  185.                 Write-Host("Resetting MasterPage ""$masterPage""");
  186.                 [void] $xmlNode.ParentNode.RemoveChild($xmlNode);
  187.             }
  188.             elseif ($xmlNode.getAttribute("Url").ToString().ToLower().Contains($masterPage.ToString().ToLower()))
  189.             {
  190.                 Write-Host("Resetting MasterPage ""$masterPage""");
  191.                 [void] $xmlNode.ParentNode.RemoveChild($xmlNode);
  192.             }
  193.         }
  194.         foreach ($minMasterPage in $script:invalidMinimalMasterPages)
  195.         {
  196.             if ($xmlNode.getAttribute("Url").ToString().ToLower().Contains($minMasterPage.ToString().ToLower()))
  197.             {
  198.                 Write-Host("Resetting MasterPage ""$minMasterPage""");
  199.                 [void] $xmlNode.ParentNode.RemoveChild($xmlNode);
  200.             }
  201.         }
  202.     }
  203.    
  204.     # Run Garbage Collect to release unused memory.
  205.     [GC]::Collect();
  206.    
  207.     # Get a collection/list of all the <Web> children nodes in <objects>.
  208.     $xmlNodeList = $manifestXml.SelectNodes("//objects:Web", $xmlNameSpaceManager);
  209.    
  210.     # Parse each child node and reset the MasterPage references.
  211.     foreach ($xmlNode in $xmlNodeList) {
  212.         foreach ($masterPage in $script:invalidMasterPages)
  213.         {
  214.             if ($xmlNode.getAttribute("MasterUrl").ToString().ToLower().Contains($masterPage.ToString().ToLower()))
  215.             {
  216.                 Write-Host("Resetting MasterPage ""$masterPage""");
  217.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  218.             }
  219.             elseif ($xmlNode.getAttribute("CustomMasterUrl").ToString().ToLower().Contains($masterPage.ToString().ToLower()))
  220.             {
  221.                 Write-Host("Resetting MasterPage ""$masterPage""");
  222.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  223.             }
  224.         }
  225.     }
  226.    
  227.     # Run Garbage Collect to release unused memory.
  228.     [GC]::Collect();
  229.    
  230.     # Get a collection/list of all the <File> children nodes in <objects>.
  231.     $xmlNodeList = $manifestXml.SelectNodes("//objects:File", $xmlNameSpaceManager);
  232.    
  233.     # Parse each child node and reset the MasterPage references.
  234.     foreach ($xmlNode in $xmlNodeList) {
  235.         foreach ($masterPage in $script:invalidMasterPages)
  236.         {
  237.             if ($xmlNode.getAttribute("Url").ToString().ToLower().Contains($masterPage.ToString().ToLower()))
  238.             {
  239.                 Write-Host("Resetting MasterPage ""$masterPage""");
  240.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  241.             }
  242.             elseif ($xmlNode.getAttribute("Name").ToString().ToLower().Contains($masterPage.ToString().ToLower()))
  243.             {
  244.                 Write-Host("Resetting MasterPage ""$masterPage""");
  245.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  246.             }
  247.             elseif ($xmlNode.getAttribute("SetupPath").ToString().ToLower().Contains($masterPage.ToString().ToLower()))
  248.             {
  249.                 Write-Host("Resetting MasterPage ""$masterPage""");
  250.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  251.             }
  252.         }
  253.         foreach ($minMasterPage in $script:invalidMinimalMasterPages)
  254.         {
  255.             if ($xmlNode.getAttribute("Url").ToString().ToLower().Contains($minMasterPage.ToString().ToLower()))
  256.             {
  257.                 Write-Host("Resetting MasterPage ""$minMasterPage""");
  258.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  259.             }
  260.             elseif ($xmlNode.getAttribute("Name").ToString().ToLower().Contains($minMasterPage.ToString().ToLower()))
  261.             {
  262.                 Write-Host("Resetting MasterPage ""$minMasterPage""");
  263.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  264.             }
  265.             elseif ($xmlNode.getAttribute("SetupPath").ToString().ToLower().Contains($minMasterPage.ToString().ToLower()))
  266.             {
  267.                 Write-Host("Resetting MasterPage ""$minMasterPage""");
  268.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  269.             }
  270.         }
  271.     }
  272.    
  273.     # Run Garbage Collect to release unused memory.
  274.     [GC]::Collect();
  275.    
  276.     # Get a collection/list of all the <Property> children nodes in <objects>.
  277.     $xmlNodeList = $manifestXml.SelectNodes("//objects:Property", $xmlNameSpaceManager);
  278.    
  279.     # Parse each child node and reset the MasterPage references.
  280.     foreach ($xmlNode in $xmlNodeList) {
  281.         foreach ($masterPage in $script:invalidMasterPages)
  282.         {
  283.             if ($xmlNode.getAttribute("Value").ToString().ToLower().Contains($masterPage.ToString().ToLower()))
  284.             {
  285.                 Write-Host("Resetting MasterPage ""$masterPage""");
  286.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  287.             }
  288.         }
  289.         foreach ($minMasterPage in $script:invalidMinimalMasterPages)
  290.         {
  291.             if ($xmlNode.getAttribute("Value").ToString().ToLower().Contains($minMasterPage.ToString().ToLower()))
  292.             {
  293.                 Write-Host("Resetting MasterPage ""$minMasterPage""");
  294.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  295.             }
  296.         }
  297.     }
  298.    
  299.     # Run Garbage Collect to release unused memory.
  300.     [GC]::Collect();
  301.    
  302.     # Get a collection/list of all the <ListItem> children nodes in <objects>.
  303.     $xmlNodeList = $manifestXml.SelectNodes("//objects:ListItem", $xmlNameSpaceManager);
  304.    
  305.     # Parse each child node and reset the MasterPage references.
  306.     foreach ($xmlNode in $xmlNodeList) {
  307.         foreach ($masterPage in $script:invalidMasterPages)
  308.         {
  309.             if ($xmlNode.getAttribute("FileUrl").ToString().ToLower().Contains($masterPage.ToString().ToLower()))
  310.             {
  311.                 Write-Host("Resetting MasterPage ""$masterPage""");
  312.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  313.             }
  314.             elseif ($xmlNode.getAttribute("Name").ToString().ToLower().Contains($masterPage.ToString().ToLower()))
  315.             {
  316.                 Write-Host("Resetting MasterPage ""$masterPage""");
  317.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  318.             }
  319.         }
  320.         foreach ($minMasterPage in $script:invalidMinimalMasterPages)
  321.         {
  322.             if ($xmlNode.getAttribute("FileUrl").ToString().ToLower().Contains($minMasterPage.ToString().ToLower()))
  323.             {
  324.                 Write-Host("Resetting MasterPage ""$minMasterPage""");
  325.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  326.             }
  327.             elseif ($xmlNode.getAttribute("Name").ToString().ToLower().Contains($minMasterPage.ToString().ToLower()))
  328.             {
  329.                 Write-Host("Resetting MasterPage ""$minMasterPage""");
  330.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  331.             }
  332.         }
  333.     }
  334.    
  335.     # Run Garbage Collect to release unused memory.
  336.     [GC]::Collect();
  337.    
  338.     Write-Host("");
  339.     Write-Host("Applying the Master Page resets in the Manifest.xml file ... ");
  340.    
  341.     $manifestXml.Save($script:manifestFile);
  342.    
  343.     # Run Garbage Collect to release unused memory.
  344.     [GC]::Collect();
  345. }
  346.  
  347. function StripRequirementsXml()
  348. {
  349.     Write-Host("");
  350.     Write-Host("Loading the contents of the Requirements XML file ... ");
  351.     Write-Host("");
  352.  
  353.     # Load in the contents of the Requirements XML file.
  354.     [System.Xml.XmlDocument] $requirementsXml = New-Object System.Xml.XmlDocument;
  355.     $requirementsXml.Load($script:requirementsFile);
  356.  
  357.     # Add a <reqs> Node-Alias for the <Requirements xmlns="abc"> node so the XPATH parsing works.
  358.     [System.Xml.XmlNamespaceManager] $xmlNameSpaceManager = New-Object System.Xml.XmlNamespaceManager($requirementsXml.Psbase.NameTable);
  359.     $xmlNameSpaceManager.AddNamespace("reqs", $requirementsXml.Requirements.xmlns);
  360.  
  361.     # Get a collection/list of all the <Requirement> children nodes in <reqs>.
  362.     $xmlNodeList = $requirementsXml.SelectNodes("//reqs:Requirement", $xmlNameSpaceManager);
  363.  
  364.     # Parse the attributes of each child node.
  365.     foreach ($xmlNode in $xmlNodeList) {
  366.         $type = $xmlNode.getAttribute("Type");
  367.         $id = $xmlNode.getAttribute("Id");
  368.         $name = $xmlNode.getAttribute("Name");
  369.         $data = $xmlNode.getAttribute("Data");
  370.  
  371.         # Remove the invalid feature if found.
  372.         if ($script:invalidFeatureIds -contains $id)
  373.         {
  374.           Write-Host("Removing ""$name""");
  375.           [void] $xmlNode.ParentNode.RemoveChild($xmlNode);
  376.         }
  377.     }
  378.  
  379.     Write-Host("");
  380.     Write-Host("Applying the removal of features and saving the new Requirements.xml file ... ");
  381.  
  382.     $requirementsXml.Save($script:requirementsFile);
  383.  
  384.     # Run Garbage Collect to release unused memory.
  385.     [GC]::Collect();
  386. }
  387.  
  388. function StripManifestXml()
  389. {
  390.     Write-Host("");
  391.     Write-Host("Loading the contents of the Manifest XML file ... ");
  392.     Write-Host("");
  393.    
  394.     # Load in the contents of the Manifest XML file.
  395.     [System.Xml.XmlDocument] $manifestXml = New-Object System.Xml.XmlDocument;
  396.     [Xml] $manifestXml.Load($script:manifestFile);
  397.    
  398.     # Add a <objects> Node-Alias for the <SPObjects xmlns="abc"> node so the XPATH parsing works.
  399.     [System.Xml.XmlNamespaceManager] $xmlNameSpaceManager = New-Object System.Xml.XmlNamespaceManager($manifestXml.Psbase.NameTable);
  400.     $xmlNameSpaceManager.AddNamespace("objects", $manifestXml.SPObjects.xmlns);
  401.    
  402.     # Get a collection/list of all the <SPObject> children nodes in <objects>.
  403.     $xmlNodeList = $manifestXml.SelectNodes("//objects:SPObject", $xmlNameSpaceManager);
  404.    
  405.     # Parse the attributes of each child node.
  406.     foreach ($xmlNode in $xmlNodeList) {
  407.         $id = $xmlNode.getAttribute("Id").ToString().ToLower();
  408.        
  409.         # Remove the invalid feature if found.
  410.         if ($script:invalidFeatureIds -contains $id)
  411.         {
  412.             Write-Host("Removing ""$id""");
  413.             [void] $xmlNode.ParentNode.RemoveChild($xmlNode);
  414.         }
  415.     }
  416.    
  417.     # Run Garbage Collect to release unused memory.
  418.     [GC]::Collect();
  419.    
  420.     # Get a collection/list of all the <List> children nodes in <objects>.
  421.     $xmlNodeList = $manifestXml.SelectNodes("//objects:List", $xmlNameSpaceManager);
  422.    
  423.     # Parse the attributes of each child node.
  424.     foreach ($xmlNode in $xmlNodeList) {
  425.         $templateFeatureId = $xmlNode.getAttribute("TemplateFeatureId").ToString().ToLower();
  426.        
  427.         # Remove the invalid feature if found.
  428.         if ($script:invalidFeatureIds -contains $templateFeatureId)
  429.         {
  430.             Write-Host("Removing ""$templateFeatureId""");
  431.             [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  432.         }
  433.     }
  434.    
  435.     # Run Garbage Collect to release unused memory.
  436.     [GC]::Collect();
  437.    
  438.     # Get a collection/list of all the <Feature> children nodes in <objects>.
  439.     $xmlNodeList = $manifestXml.SelectNodes("//objects:Feature", $xmlNameSpaceManager);
  440.    
  441.     # Parse the attributes of each child node.
  442.     foreach ($xmlNode in $xmlNodeList) {
  443.         $id = $xmlNode.getAttribute("Id").ToString().ToLower();
  444.        
  445.         # Remove the invalid feature if found.
  446.         if ($script:invalidFeatureIds -contains $id)
  447.         {
  448.             Write-Host("Removing ""$id""");
  449.             [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  450.         }
  451.     }
  452.    
  453.     # Run Garbage Collect to release unused memory.
  454.     [GC]::Collect();
  455.    
  456.     # Get a collection/list of all the <ContentType> children nodes in <objects>.
  457.     $xmlNodeList = $manifestXml.SelectNodes("//objects:ContentType", $xmlNameSpaceManager);
  458.    
  459.     # Parse the attributes of each child node.
  460.     foreach ($xmlNode in $xmlNodeList) {
  461.         $featureId = $xmlNode.getAttribute("FeatureId").ToString().ToLower();
  462.        
  463.         # Remove the invalid feature if found.
  464.         if ($featureId.Contains("{"))
  465.         {
  466.             if ($script:invalidFeatureIds -contains $featureId.Substring(1, 36))
  467.             {
  468.                 Write-Host("Removing ""$featureId""");
  469.                 [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  470.             }
  471.         }
  472.     }
  473.    
  474.     # Run Garbage Collect to release unused memory.
  475.     [GC]::Collect();
  476.    
  477.     # Get a collection/list of all the <DocumentLibrary> children nodes..
  478.     $xmlNodeList = $manifestXml.SelectNodes("//objects:DocumentLibrary", $xmlNameSpaceManager);
  479.    
  480.     # Parse the attributes of each child node.
  481.     foreach ($xmlNode in $xmlNodeList) {
  482.         $templateFeatureId = $xmlNode.getAttribute("TemplateFeatureId").ToString().ToLower();
  483.        
  484.         # Remove the invalid feature if found.
  485.         if ($script:invalidFeatureIds -contains $templateFeatureId)
  486.         {
  487.             Write-Host("Removing ""$templateFeatureId""");
  488.             [void] $xmlNode.ParentNode.ParentNode.RemoveChild($xmlNode.ParentNode);
  489.         }
  490.     }
  491.    
  492.     # Run Garbage Collect to release unused memory.
  493.     [GC]::Collect();
  494.    
  495.     Write-Host("");
  496.     Write-Host("Applying the removal of features and saving the new Manifest.xml file ... ");
  497.    
  498.     $manifestXml.Save($script:manifestFile);
  499.    
  500.     # Run Garbage Collect to release unused memory.
  501.     [GC]::Collect();
  502. }
  503.  
  504. # Extract the contents of the CMP file to temporary directory.
  505. ExtractExportFile;
  506.  
  507. # Remove unwanted features and solutions from the Requirements.xml file.
  508. StripRequirementsXml;
  509.  
  510. # Remove unwanted features and solutions from the Manifest.xml file.
  511. StripManifestXml;
  512.  
  513. # Reset Custom MasterPage references in the Manifest.xml file.
  514. ResetMasterPageReferences;
  515.  
  516. # Generate a DDF File.
  517. CreateDDF;
  518.  
  519. # Compresses the final CMP package.
  520. CreateCMP;
  521.  
  522. # Clean up and remove temporary files and directories.
  523. CleanTemporaryFiles;
  524.  
  525. Write-Host("");
  526. Write-Host("Done!");
  527. Write-Host("");
  528. Write-Host("");
  529.  
  530. Exit;
  531.