Olav Aukan Getting information off the Internet is like taking a drink from a fire hydrant…

30Mar/11

How To Backup And Restore IIS 7 Metabase With PowerShell

As I mentioned in my last blog post I've been playing around with PowerShell for SharePoint automation for a while now. One of the problems with SharePoint is it's complicated architecture that makes a seemingly simple task - backing up the system - incredibly complicated. The complexity often comes from the fact that there are so many options to choose from. There are SQL backups of the content databases, Stsadm.exe backups of whole or parts of the farm, VMWare snapshots of the server (which by the way is not supported by Microsoft), file backups of the 12 hive, solution file backups, web.config backups, IIS backups, third party backup solutions that do some or all of these things etc. etc. etc.

This blog post is not going to solve those issues for you, but it will at least give you one part of the puzzle: how to backup and restore the IIS metabase with PowerShell.

# This function performs a backup of the IIS Metabase
function SP-Backup-IIS {

	param (
		[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]
		[string]
		$BackupFolder		
	)
	
	process {
	
		UI-PrintAction "Attempting to backup the Internet Information Services Metabase"
		
		Write-Host "Checking IIS version..."
		Write-Host
		
		$IISVersion = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\InetStp | select SetupString, *Version*)
		
		Write-Host "IIS Version:"
		$IISVersion
		Write-Host
		
		$ComputerName = Get-Content Env:Computername
		$Time = [DateTime]::Now.ToString().Replace(":",".").Replace(" ","_")
		$BackupName = "IIS-Metadata-$ComputerName-$Time"
		$BackupFileName = "$BackupName.zip"
		
	    switch ($IISVersion.MajorVersion) 
		{
			# IIS 7
			7 {
				$BackupFilePath = Join-Path -Path $BackupFolder -ChildPath $BackupFileName

				Write-Host "Starting backup of IIS metadata."
				Write-Host
				
				#Perform backup
				& $env:windir\system32\inetsrv\appcmd.exe add backup $BackupName				
				
				#Zip and copy backup to destination
				$IISRootBackupFolder = Get-Item $env:windir\system32\inetsrv\backup
				$IISBackupFolder = Join-Path -Path $IISRootBackupFolder -ChildPath $BackupName								
				Zip-Compress-Folder $IISBackupFolder $BackupFilePath
												
				Write-Host "Backup Complete"
			}
			
			# Other version of IIS
			default { Write-Host "This version of IIS is not supported in this script." -ForegroundColor Yellow }
		}
		Write-Host
	}
}

# This function restores a backup of the IIS Metabase
function SP-Restore-IIS {

	param (
		[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]
		[string]
		$BackupName		
	)
	
	process {
	
		UI-PrintAction "Attempting to restore the Internet Information Services Metabase"
		
		Write-Host "Checking IIS version..."
		Write-Host
		
		$IISVersion = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\InetStp | select SetupString, *Version*)
		
		Write-Host "IIS Version:"
		$IISVersion
		Write-Host
				
	    switch ($IISVersion.MajorVersion) 
		{
			# IIS 7
			7 {

				Write-Host "Starting restore of IIS metadata."
				Write-Host
				
				#Perform restore
				& $env:windir\system32\inetsrv\appcmd.exe restore backup $BackupName				
								
				Write-Host
				Write-Host "Restore Complete"
			}
			
			#Other version of IIS
			default { Write-Host "This version of IIS is not supported in this script." -ForegroundColor Yellow }
		}
		Write-Host
	}
}


# This function zips the contents of a folder
function Zip-Compress-Folder { 

	param (
		[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]
		[string]
		$SourceFolder,		

		[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1)]
		[string]
		$ZipFileName		
	)
	
	process {
		
		Write-Host
		Write-Host "Creating new zip file."
		Write-Host
		Write-Host "Source: $SourceFolder"
		Write-Host "Destination: $ZipFileName"
		Write-Host
		
		$ParentFolder = (Get-Item $SourceFolder).Parent.FullName
		
		# Check if file exists
		if (test-path $ZipFileName) { 
		  Write-Host "The file $ZipFileName already exists." -ForegroundColor Yellow
		  return
		} 
		
		# Create zip file		
		Set-Content $ZipFileName ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
		(Dir $ZipFileName).IsReadOnly = $false 
		$ZipFile = (New-Object -com shell.application).NameSpace($ZipFileName) 
		
		Write-Host "Adding $SourceFolder to the zip file."
		Write-Host
				
		$ZipFile.CopyHere((Get-Item $SourceFolder).FullName)
		
		Write-Host "Zip file created."
		Write-Host
	}
}

SP-Backup-IIS will backup the IIS metabase, zip the backup folder and copy it to the location you specify in the $BackupFolder parameter. The SP-Restore-IIS will restore the IIS metabase from the backup you specify in the $BackupName parameter. This parameter corresponds to the name of the backup folder in 'C:\Windows\System32\inetsrv\backup'. You could expand on this by having the restore script first get your zip file and extract it to the backup folder. Happy scripting!

Filed under: Uncategorized 34 Comments
21Mar/11

How to backup SharePoint using PowerShell

Lately I've been reading up on - and experimenting with - PowerShell to automate alot of the tings I do in SharePoint. The original motivation was a deployment gone bad (ie. too many manual steps + too little time = too many errors) and it got me rethinking my whole approach to managing SharePoint.

My previous attempts at automating the build -> package -> deploy process with a .bat file calling MSBuild and STSADM commands had failed miserably about two years ago. It would not wait for the solution to finish retracting before trying to remove it, or it would try to activate a feature before the solution was finished deploying, etc. Also, since it was one giant monolithic script, any errors early on in the process would cause all sorts of problems.

There are ways to deal with this in .bat files, but they don't even come close to the cool stuff you can do with PowerShell! Therefore I'm planning on writing a couple of posts about using PowerShell to manage SharePoint based on the things I've been trying out so far. Keep in mind that I'm still learning and some of the stuff I write about might be stupid, inefficient or downright wrong. With that disclaimer out of the way I present my first PowerShell script: Performing a full farm backup.

# This function performs a complete backup of the local farm
function SP-Backup-Farm {

	param (
		[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]
		[string]
		$BackupFolder
	)

	process {

		Write-Host "Attempting full backup of the farm."

		# Create the backup settings
		$Settings = [Microsoft.SharePoint.Administration.Backup.SPBackupRestoreSettings]::GetBackupSettings($BackupFolder, "Full");

		# Set optional operation parameters
		$Settings.IsVerbose = $true;
		$Settings.UpdateProgress = 10;
		$Settings.BackupThreads = 10;

		# File size details
		$BackupSize = New-Object UInt64
		$DiskSize = New-Object UInt64
		$DiskFreeSize = New-Object UInt64

		Write-Host "Backup Location:" $BackupFolder

		# Check that the target folder exists
		if (Test-Path $BackupFolder)
		{
			Write-Host "Backup Location Exists: True"
			Write-Host

			# Backup operation details
			$BackupID = [Microsoft.SharePoint.Administration.Backup.SPBackupRestoreConsole]::CreateBackupRestore($Settings);
			$BackupObjects = [Microsoft.SharePoint.Administration.Backup.SPBackupRestoreConsole]::FindItems($BackupID, "Farm");

			# Get file size info
			$BackupSize = [Microsoft.SharePoint.Administration.Backup.SPBackupRestoreConsole]::DiskSizeRequired($BackupID)
			[void][Microsoft.SharePoint.Administration.Backup.SPBackupRestoreConsole]::DiskSize($BackupFolder, [ref]$DiskFreeSize, [ref]$DiskSize)

			# Check if there is enough free disk space
			$HasEnoughSpace = $false
			if ($DiskFreeSize -gt $BackupSize)
			{
				$HasEnoughSpace = $true
			}

			$BackupSizeString = Util-Convert-FileSizeToString $BackupSize
			$DiskSizeString = Util-Convert-FileSizeToString $DiskSize
			$DiskFreeSizeString = Util-Convert-FileSizeToString $DiskFreeSize

			Write-Host "Total Disk Space:" $DiskSizeString
			Write-Host "Free Disk Space:" $DiskFreeSizeString
			Write-Host "Required Disk Space:" $BackupSizeString
			Write-Host

			if($HasEnoughSpace)
			{
				Write-Host "Sufficient Free Disk Space: True"

				# Set the backup as the active job and run it
				if ([Microsoft.SharePoint.Administration.Backup.SPBackupRestoreConsole]::SetActive($BackupID))
				{
					$BackupObjectCount = $BackupObjects.Count

					Write-Host "Successfully set backup job as the active job."
					Write-Host "Backup consists of $BackupObjectCount object(s)"
					Write-Host
					Write-Host "Backup Started"
					Write-Host

					foreach($BackupObject in $BackupObjects)
					{
						if (([Microsoft.SharePoint.Administration.Backup.SPBackupRestoreConsole]::Run($BackupID, $BackupObject)))
						{
							Write-Host "Backup Completed"
						}
						else
						{
							Write-host "An unexpected error occured!" -ForegroundColor Yellow
							Write-Host "Backup Failed" -ForegroundColor Yellow
						}
					}
				}
				else
				{
					Write-Host "Unable to set backup job as the active job." -ForegroundColor Yellow
					Write-Host "Backup Failed." -ForegroundColor Yellow
				}
			}
			else
			{
				Write-Host "Sufficient Free Disk Space: False" -ForegroundColor Yellow
				Write-Host "Backup Failed" -ForegroundColor Yellow
			}
		}
		else
		{
			Write-Host "Backup Location Exists: False" -ForegroundColor Yellow
			Write-Host "Backup folder doesn't exist or the service account does not have read/write access to it." -ForegroundColor Yellow
			Write-Host "Backup Failed." -ForegroundColor Yellow
		}

		Write-Host

		# Clean up the operation
		if (!$BackupID -eq $null)
		{
			[void][Microsoft.SharePoint.Administration.Backup.SPBackupRestoreConsole]::Remove($BackupID)
		}
	}
}

# This function returns a "user friendly" display value for a filesize in bytes
function Util-Convert-FileSizeToString {

    param (
		[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]
		[int64]
		$sizeInBytes
	)

    switch ($sizeInBytes)
    {
        {$sizeInBytes -ge 1TB} {"{0:n$sigDigits}" -f ($sizeInBytes/1TB) + " TB" ; break}
        {$sizeInBytes -ge 1GB} {"{0:n$sigDigits}" -f ($sizeInBytes/1GB) + " GB" ; break}
        {$sizeInBytes -ge 1MB} {"{0:n$sigDigits}" -f ($sizeInBytes/1MB) + " MB" ; break}
        {$sizeInBytes -ge 1KB} {"{0:n$sigDigits}" -f ($sizeInBytes/1KB) + " KB" ; break}
        Default { "{0:n$sigDigits}" -f $sizeInBytes + " Bytes" }
    }
}

The convert bytes to string function was something I found on another blog and adapted to PowerShell, so I can't really take credit for that one. Also it took about 3 hours to do a full backup on my VMWare machine with about 50GB of content databases. Your milage may vary...

21Mar/11

How to ghost an unghosted content type

A while back I wrote a post about content types becoming unghosted when you edit them in the UI and there not being any way of reversing this without directly modifying the content database. That would put you in an unsupported state, so obviously it's not an option for most people. But as it turns out there is a supported way of doing this after all, it's just not very well documented...

A colleague of mine was having this same problem and informed me that he was able to fix it with the STSADM Deactivatefeature operation by adding the -force parameter.

Let us see what the Holy Gospel according to TechNet has to say about the -force parameter:

"force - Forces the feature to be uninstalled."

In other words, not much help to be found in the scriptures. But it works! When the feature containing the unghosted content type(s) is deactivated with the -force parameter the Definition column in the ContentTypes table goes back to NULL, meaning it is now using the XML definition again.

You can then use the gl-propagatecontenttype custom STSADM command from Gary Lapointe with the -updatefields parameter to push changes in the site content type down to the list content type.