This post is the second of a series explaining how to audit your SharePoint farm.
This can be achieved by some PowerShell code executed on one of the farms servers.
$ftcPath = "filepath to store the full trust farm solutions" (Get-SPFarm).Solutions | % { $_.SolutionFile.SaveAs($ftcPath + "\" + $_.Name) }
$sbsPath = "filepath to store the sandboxed solutions" $site = Get-SPSite "http://<url to site>" $listTemplate = [Microsoft.SharePoint.SPListTemplateType]::SolutionCatalog $solGallery = $_.GetCatalog($listTemplate) $solGallery.Items | % { [System.IO.FileStream]$outStream = New-Object System.IO.FileStream(($sbsPath+"\"+$_.File.Name), [System.IO.FileMode]::Create); $fileData = $_.File.OpenBinary(); $outStream.Write($fileData, 0, $fileData.Length); $outStream.Close(); }
$appPath = "filepath to store the apps" $instances = Get-SPAppInstance -Web "http://<url to web>" foreach ($instance in $instances) { $filename = [RegEx]::Replace($instance.Title, "[{0}]" -f ([RegEx]::Escape([String][System.IO.Path]::GetInvalidFileNameChars())), '')+".app" Export-SPAppPackage -App $instance.App -Path ($webPath + "\"+ $filename) }
The code above just illustrates how to get solutions and apps from a single instance of a site or web.
To extract all code customizations from the farm you can use the following complete script. It retrieves them from all sites and webs which are accessible by the account executing the script.
It creates a folder named after the current date, subfolders for full trust solutions, sandboxed solutions and apps as well as subfolders for each site or web that contains a customisation.
During execution it logs the processed sites, solutions and apps.
Exporting SharePoint Customizations Saving farm solutions to C:\Users\sp_admin\temp\20141009\FullTrust ftc.text1.wsp...Done ftc.text2.wsp...Done ftc.text3.wsp...Done Saving sandboxed solutions to C:\Users\sp_admin\temp\20141009\Sandboxed http://my.sp2013.dev.local...None http://my.sp2013.dev.local/personal/sp_admin...None http://my.sp2013.dev.local/personal/sp_searchcontent...None http://portal.sp2013.dev.local...1 MyCompany.Intranet.WebParts.wsp...Done http://portal.sp2013.dev.local/search...1 MyCompany.Intranet.Search.wsp...Done http://portal.sp2013.dev.local/sites/appcat...None http://portal.sp2013.dev.local/sites/community...None http://portal.sp2013.dev.local/sites/developer...None http://portal.sp2013.dev.local/sites/project...None http://portal.sp2013.dev.local/sites/team...None MyCompany.Intranet.WebParts.wsp...Done Saving Apps to (C:\Users\sp_admin\temp\20141009\Apps http://my.sp2013.dev.local...None http://my.sp2013.dev.local/personal/sp_admin...None http://portal.sp2013.dev.local...1 OneNoteClassNotebookCreator.app...Done http://portal.sp2013.dev.local/Docs...None http://app-5cb2120e29a29f.app.dev.local/edu1note...None http://portal.sp2013.dev.local/News...2 OneNoteClassNotebookCreator.app...Done MyNewsApp.app...Done http://portal.sp2013.dev.local/SearchCenter...None http://portal.sp2013.dev.local/SiteDirectory...None http://portal.sp2013.dev.local/Wiki...None http://portal.sp2013.dev.local/search...None http://portal.sp2013.dev.local/sites/appcat...None http://portal.sp2013.dev.local/sites/community...None http://portal.sp2013.dev.local/sites/developer...None http://portal.sp2013.dev.local/sites/project...None http://portal.sp2013.dev.local/sites/team...1 OneNoteClassNotebookCreator.app...Done Finished
Without further ado, her comes the complete script
### ### Exports all SharePoint farm solutions, sandboxed solutions and apps to the file system ### Author: Matthias Einig, http://www.twitter.com/mattein ### Url: http: www.spcaf.com ### Add-PSSnapin Microsoft.SharePoint.PowerShell Write-Host "Exporting SharePoint Customizations" # Preparation: (re)create folder named after the current date to collect all customizations $foldername = (Get-Date -Format "yyyyMMdd").ToString() rmdir $foldername -Recurse -ErrorAction SilentlyContinue $basePath = (mkdir $foldername).FullName # Collect all disposable objects Start-SPAssignment -Global try { # Step 1: Save all farm solutions $ftcPath = (mkdir ($basePath +"\FullTrust")).FullName Write-Host " Saving farm solutions to $ftcPath" (Get-SPFarm).Solutions | % { Write-Host " $($_.Name)..." -NoNewline try{ $_.SolutionFile.SaveAs($ftcPath + "\" + $_.Name) Write-Host -ForegroundColor Green "Done" } catch{ Write-Host -ForegroundColor Red "Failed" Write-Error $_.Exception.Message; } } # Step 2: Save all sandboxed solutions $sbsPath = (mkdir($basePath +"\Sandboxed")).FullName Write-Host " Saving sandboxed solutions to $sbsPath" # Get all accessible SharePoint sites in the farm # Warning: this might take a while, so consider to limit it to specific sites # Also it might retrieve duplicate solutions (in different versions) form different sites Get-SPSite -limit All -WarningAction SilentlyContinue | % { Write-Host " $($_.Url)..." -NoNewline try{ $listTemplate = [Microsoft.SharePoint.SPListTemplateType]::SolutionCatalog $solGallery = $_.GetCatalog($listTemplate) if($solGallery.ItemCount -gt 0) { Write-Host -ForegroundColor Green $solGallery.ItemCount # create subfolder for site with safe folder name removing the protocol and special chars $subfolder = [RegEx]::Replace($_.Url.Substring($_.url.IndexOf(":")+3), "[{0}]" -f ([RegEx]::Escape([String][System.IO.Path]::GetInvalidFileNameChars())), '') $sitePath = (mkdir ($sbsPath +"\"+ $subfolder)).FullName # save all sandboxed solutions of this site $solGallery.Items | % { Write-Host " $($_.File.Name)..." -NoNewline try{ [System.IO.FileStream]$outStream = New-Object System.IO.FileStream(($sitePath+"\"+$_.File.Name), [System.IO.FileMode]::Create); $fileData = $_.File.OpenBinary(); $outStream.Write($fileData, 0, $fileData.Length); $outStream.Close(); Write-Host -ForegroundColor Green "Done" } catch{ Write-Host -ForegroundColor Red "Failed" Write-Error $_.Exception.Message; } } } else{ Write-Host "None" } } catch{ Write-Host -ForegroundColor Red "Failed" Write-Error $_.Exception.Message; } } # Step 3: Save all SharePoint Apps $appPath = (mkdir($basePath +"\Apps")).FullName Write-Host " Saving Apps to $appPath" # Get all accessible SharePoint webs in the farm # Warning: this might take a while, so consider to limit it to specific sites # Also it might retrieve duplicate solutions (in different versions) form different sites Get-SPSite -Limit All -WarningAction SilentlyContinue | Get-SPWeb -Limit All -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | % { Write-Host " $($_.Url)..." -NoNewline try{ $instances = Get-SPAppInstance -Web $_.Url if($instances.Count -gt 0) { Write-Host -ForegroundColor Green $instances.Count # create subfolder for web with safe folder name removing the protocol and special chars $subfolder = [RegEx]::Replace($_.Url.Substring($_.url.IndexOf(":")+3), "[{0}]" -f ([RegEx]::Escape([String][System.IO.Path]::GetInvalidFileNameChars())), '') $webPath = (mkdir ($appPath +"\"+ $subfolder)).FullName # export all apps foreach ($instance in $instances) { # create safe file name $filename = [RegEx]::Replace($instance.Title, "[{0}]" -f ([RegEx]::Escape([String][System.IO.Path]::GetInvalidFileNameChars())), '')+".app" Write-Host " $filename..." -NoNewline try{ Export-SPAppPackage -App $instance.App -Path ($webPath + "\"+ $filename) Write-Host -ForegroundColor Green "Done" } catch{ Write-Host -ForegroundColor Red "Failed" Write-Error $_.Exception.Message; } } } else{ Write-Host "None" } } catch{ Write-Host -ForegroundColor Red "Failed" Write-Error $_.Exception.Message; } } } finally{ #Cleanup disposable objects Stop-SPAssignment -Global Write-Host "Finished" }
The entire script can be downloaded from GitHub
Now that you have extracted all code customizations you might wonder what to do with them.
» Read on at “SharePoint health check (3): Auditing Customizations”