SSMS : copier sa liste de serveurs inscrits sur un autre ordinateur

Dans SQL Server Management Studio, la liste des serveurs inscrits est une fonctionnalité bien pratique. Ma propre liste comprend près de 300 serveurs : les miens et ceux de mes clients. Maintenant que le travail à distance est une réalité quotidienne, j'ai besoin de transférer tout cela de mon ordinateur portable à mon ordinateur de bureau.

Il existe bien une fonction d'exportation et d'importation dans SSMS, le seul inconvénient est qu'il n'est pas possible de transférer les mots de passe (ceux pour les connexions SQL) : ils sont chiffrés avec la clé de l'utilisateur. En changeant d'ordinateur, l'importation d'un fichier embarquant les mots de passe chiffrés se passe mal (et d'importer un tel fichier peut même faire planter SSMS !).

Je n'ai pas vraiment envie de resaisir 300 mots de passe, et heureusement il existe une solution !

Une fois votre fichier d'exportation généré, avec les mots de passe bien sûr, le script powershell suivant va vous permettre d'effectuer l'opération de déchiffrement, puis de rechiffrement.

  • Etape 1 : sur l'ordinateur source,
    • Lancer le script,
    • à l'invite entrez le nom du fichier d'export à déchiffrer (par exemple messerveurs.regsrvr),
    • à l'invite tapez : Decrypt
    • Le fichier est décrypté (les mots de passe sont réécrits déchiffrés et stockés en base64)
  • Etape 2 : copier le fichier sur l'ordinateur destination
  • Etape 3 : sur l'ordinateur de destination
    • Lancer le script,
    • à l'invite, entrez le nom du fichier à rechiffrer
    • à l'invite tapez : Encrypt
    • Le fichier est rechiffré sur le nouvel ordinateur.

Il n'y a plus qu'à utiliser la fonction d'importation de SSMS...

Ce code de déchiffrement peut être trouvé sur certains sites (par exemple dbatools.io) , mais j'y ai fait une petite modification. En effet, le code d'origine déchiffre les mots de passe en texte clair : c'est pratique pour les lire, mais pour des mots de passe complexes certains caractères ne sont pas correctement pris en charge lors de la sauvegarde en XML et le fichier une fois rechiffré est corrompu.

#SSMSDecryptEncryptRegServers.ps1
#Use it to decrypt and reencrypt passwords of SSMS registered servers exported file.
param(
    [Parameter(Mandatory=$true)]
    [string] $FileName,
    [Parameter(Mandatory=$true)][ValidateSet('Decrypt', 'Encrypt')]
    [string] $Operation
)
[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
$ErrorActionPreference = 'Stop'
function Protect-String([string] $clearText)
{
    return [System.Convert]::ToBase64String([System.Security.Cryptography.ProtectedData]::Protect([System.Text.Encoding]::Unicode.GetBytes($clearText), $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser))
}
function Unprotect-String([string] $base64String)
{
    return [System.Text.Encoding]::Unicode.GetString([System.Security.Cryptography.ProtectedData]::Unprotect([System.Convert]::FromBase64String($base64String), $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser))
}
$document = [xml] (Get-Content (Resolve-Path $FileName))
$nsm = New-Object 'System.Xml.XmlNamespaceManager' ($document.NameTable)
$nsm.AddNamespace('rs', 'http://schemas.microsoft.com/sqlserver/RegisteredServers/2007/08')
$attr = $document.DocumentElement.GetAttribute('plainText')
echo $attr
if ($attr -eq '' -and $Operation -ieq 'Encrypt')
{
    throw "The file does not contain plaintext passwords."
}
if ($attr -ne '' -and $Operation -ieq 'Decrypt')
{
    throw "The file does not contain encrypted passwords."
}
$servers = $document.SelectNodes("//rs:RegisteredServer", $nsm)
foreach ($server in $servers)
{
    $connString = $server.ConnectionStringWithEncryptedPassword.InnerText
    if ($connString -inotmatch 'password="?([^";<]+)"?') {continue}
    $password = $Matches[1]

    if ($Operation -ieq 'Decrypt')
    {
        $password = Unprotect-String $password   
        # store in base64 for special characters
         $password = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($password))
    }
    if ($Operation -ieq 'Encrypt')
    {
        # decode base64 before protecting
        $password=[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($password))
        $password = Protect-String $password
    }
    $connString = $connString -ireplace 'password="?([^";<]+)"?', "password=`"$password`""
    echo " "
    echo $server.ConnectionStringWithEncryptedPassword.InnerText
    $server.ConnectionStringWithEncryptedPassword.InnerText = $connString
    echo $connString
}
if ($Operation -ieq 'Decrypt')
{
    $document.DocumentElement.SetAttribute('plainText', 'true')
}
else
{
    $document.DocumentElement.RemoveAttribute('plainText')
}
$document.Save((Resolve-Path $FileName))

Bon export/import de votre liste de serveurs !