Dans SharePoint, les livrables (WSP) déployés sont stockés dans la base de données de configuration de la ferme. Dans certains cas (perte des livrables, des sources…) on peut avoir besoin de récupérer ces fichiers WSP.
Habituellement, c’est très simple. Il suffit d’itérer sur les objets SPSolution de l’objet SPFarm.Solutions et d’appeler la méthode SaveAs pour récupérer les fichiers en local :
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
$farm = [Microsoft.SharePoint.Administration.SPFarm]::Local
$farm.Solutions | % { $_.SolutionFile.SaveAs("C:\\" + $_.Name) }
Cependant, j’ai été confronté à un cas où la ferme était endommagée (niveau applicatif) et où c’était très difficile d’en remonter une en réinstallant SharePoint et en attachant les bases.
Une solution fut de faire un peu d’ADO.NET sur la base de configuration de la ferme. Comme dans ce cas on part du postulat que, de toutes façons, la ferme ne redémarrera pas et qu’une réinstallation complète sera faite, on peut s’autoriser de requêter directement en SQL.
Après un peu d’introspection dans la base, j’ai fini par trouver que les binaires sont stockés dans la table Binaries (logique…) mais uniquement avec un guid comme identifiant. Pour retrouver le nom de fichier correspondant à chaque binaire, il faut faire une jointure sur la table Objects.
Les colonnes sont donc Name (chaîne de caractères) et FileImage (tableau d’octets).
Voici le script mettant tout ça en musique :
$sql = new-object System.Data.SqlClient.SqlConnection
$sql.ConnectionString = "Data Source=localhost;Initial Catalog=SharePoint_Config;Integrated Security=True"
$sqlcmd = new-object System.Data.SqlClient.SqlCommand
$sqlcmd.Connection = $sql
$sqlcmd.CommandText = "SELECT Name, FileImage FROM Objects RIGHT JOIN Binaries ON Objects.Id = Binaries.ObjectId"
$sql.Open()
$reader = $sqlcmd.ExecuteReader()
while ($reader.Read())
{
[string]$filename = $reader.GetString(0)
$bytes = $reader.GetValue(1)
$fs = new-object IO.FileStream("$([Environment]::CurrentDirectory)\$($filename)", [IO.FileMode]::Create)
$fs.Write($bytes, 0, $bytes.Count)
$fs.Close()
}
$reader.Dispose()
$sql.Close()
trap
{
Write-Error "$($_.Exception.Message)"
if ($fs -ne $null) { $fs.Close() }
if ($reader -ne $null) { $reader.Dispose() }
if ($sql -ne $null) { $sql.Close() }
}
Il ne faut pas abuser de cette possibilité (et ça ne remplace nullement d’avoir des backups ainsi que les sources des WSP) mais, quand notre SharePoint préféré a été piétiné par un dinosaure, c’est toujours bon de pouvoir au moins sauver les meubles
.