mercredi 8 avril 2009

Déploiement de la base de données

La partie précédente de cet article se trouve ICI.

Avant de déployer notre site Web, nous devons installer la base de données : quel portail Web ne possède pas de base de données aujourd'hui ? :)

Pour celà, notre Solution Accelerator va faire le travail pour nous. Dans l'article précédent, je vous ai présenté l'interface graphique : nous allons donc l'utiliser !

Nous allons cliquer sur notre premier menu afin de pouvoir déployer notre base de données !

Nous découvrons donc l'interface graphique dédiée :

Elle est volontairement sobre !
En effet, nous n'avons qu'à saisir le nom du serveur SQL ainsi que le nom de l'instance et cliquer sur OK...

Dans cette interface, nous avons tout de même prévu de pouvoir spécifier le compte avec lequel faire ce déploiement : visible tout en haut de la fenêtre. En effet, nous avons la possibilité d'utiliser les informations de connexion de l'utilisateur courant ou bien d'en spécifier d'autres.

Vous pouvez remarquer qu'en bas de la fenêtre il y a un grand espace vide... Nous n'allons pas le laisser vide ! En effet, cette zone est en fait réservée à l'affichage des informations et des erreurs !
Chaque étape du script est décrite dans cette zone ainsi que les éventuelles erreurs : ce sont les fonctions Display-Info ou Display-Error qui s'en chargeront :

### <summary>
### Adds a log.
### </summary>
### <param name="str">Message to add</param>
### <param name="strType">Message type</param>
function Log-Message($str, $strType)
{
    $strDate = [DateTime]::Now.ToString('G')
    $strType = $strType.ToUpper()
    $strLine = "[$strDate] $strType: $str"
    Write-Verbose $strLine
    $script:strLog += $strLine + "`r`n"
}

### <summary>
### Displays error message.
### </summary>
### <param name="str">Message to display</param>
function Display-Error($str)
{
    $LblInfo.Text = ''
    $LblError.Text = $str
    $LblError.Refresh()
    Log-Message $str 'Error'
}

### <summary>
### Display information message.
### </summary>
### <param name="str">Message to display</param>
function Display-Info($str)
{
    $LblInfo.Text = $str
    $LblInfo.Refresh()
    Log-Message $str 'Info'
}

Biensûr ces informations et erreurs sont enregistrées dans nos logs, d'où la fonction Log-Message qui ne fait qu'ajouter une date de génération du message (information ou erreur) !

Bref vous l'aurez compris, la traçabilité est importante et doit être présente dans nos scripts !

Maintenant que la présentation de l'interface est faite, nous pouvons passer à la partie la plus interessante : le traitement fait en PowerShell lorsqu'on appuie sur le bouton !

Avant tout, nous devons vérifier que nous avons tous les pré-requis nécessaires pour faire notre déploiement : les scripts SQL que nous exécuterons !

Pour celà, nous allons déclarer quelques variables qui seront visibles dans tout notre script :

### <summary>
### Path to current script.
### </summary>
$script:strPath = $MyInvocation.MyCommand.Path

### <summary>
### SQL scripts to execute during installation step.
### Order is essential.
### </summary>
$script:arrSQLGenerateDBScripts = 'CreateDatabase.sql',
                                  'ProvisioningDatabase.sql'
### <summary>
### Installation logs.
### </summary>
$script:strLog = ''

On peut remarquer ici l'utilisation de la variable $MyInvocation très utile puisqu'elle nous donne des informations sur notre dernière commande : ici on récupère le chemin où se trouve notre script PowerShell !

La variable arrSQLGenerateDBScripts contient une liste ordonnée de nos scripts SQL.

Enfin, strLog est une simple chaîne de caractères qui fera office de log (il faut garder une trace des actions et des erreurs !).

Nous allons dans un premier temps vérifier que les fichiers sont présents :

### <summary>
### Checks SQL files.
### </summary>
### <return>True on success, false otherwise</return>
function Check-SQLFiles()
{
    trap {
        $blnRes = $false
        continue
    }
    $tab = $script:strPath.Split('\')
    $strPath = ''
    for ($i = 0$i -lt $tab.length - 2$i++) {
        $strPath += $tab[$i+ '\'
    }
    $blnRes = $true
    $bln = $true
    Display-Info "Checking files in $strPath..."
    if ($script:arrSQLGenerateDBScripts.Length -gt 0) {
        foreach ($strFile in $script:arrSQLGenerateDBScripts) {
            $bln = Test-Path "$strPath\Database\$strFile"
            if ($bln -eq $false) { break }
        }
        $blnRes = $blnRes -and $bln
    }
    if ($blnRes) {
        Display-Info "Installation files successfully checked"
    } else {
        Display-Error "Installation files missing."
    }
    
    return $blnRes
}

Cette fonction vérifie que nos scripts SQL contenus dans la variable de script existent dans le répertoire ../Database.

Maintenant que nous sommes certains de la présence de nos scripts à exécuter nous allons... les exécuter ! :)
Pour celà, nous devons biensûr nous connecter à notre base de données grâce à une chaîne de connexion :

### <summary>
### Generate the SQL connection string.
### </summary>
### <param name="strDB">Database name</param>
### <return>SQL connection string</return>
function Generate-StringConnection($strDB)
{
    $strInstance = $TxtDBServer.Text
    if ($TxtDBInstance.Text.Length -gt 0) {
        $strInstance += '\' + $TxtDBInstance.Text
    }
    $strInstance = $strInstance.ToUpper()
    $objBuilder = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
    $objBuilder['Application Name'= 'Web Portal'
    $objBuilder['Data Source'= $strInstance
    $objBuilder['Initial Catalog'= $strDB

    if ($ChkWindowsAuth.Checked -eq $false) {
        $objBuilder['User Id'= $TxtLogin.Text
        $objBuilder['Password'= $TxtPassword.Text
    } else {
        $objBuilder['Integrated Security'= $true
    }

    return $objBuilder.ConnectionString
}

Cette fonction prend en paramètre le nom de la base de données qui sera affecté au paramètre "Initial Catalog" de notre chaîne de connexion.
Vous pouvez remarquer que si aucun nom d'instance n'est renseigné dans l'interface graphique, nous utilisons le nom du serveur (instance par défaut !).

Ensuite, nous pouvons écrire notre fonction qui va exécuter un script SQL :

### <summary>
### Executes a SQL script.
### </summary>
### <param name="strScript">Script full name</param>
### <param name="objSQL">SQL connection</param>
### <return>True on success, false otherwise</return>
function Execute-SQLScript($objSQL, $strScript)
{
    trap {
        Display-Error $_.Exception.Message
        $bln = $false
        continue
    }
    $bln = Test-Path $strScript
    if ($bln) {
        Display-Info "Executing script $strScript..."
        $objSQLCmd = New-Object System.Data.SqlClient.SqlCommand
        $objSQLCmd.Connection = $objSQL
        $strContent = ''
        foreach ($strLine in Get-Content $strScript) {
            if ($bln -eq $false) { return $bln }
            if ($strLine.ToUpper() -match "^\s*GO\s*$") {
                $objSQLCmd.CommandText = $strContent
                $objSQLCmd.ExecuteNonQuery() | Out-Null
                $strContent = ''
            } else {
                $strContent += $strLine + "`r`n"
            }
        }
    } else {
        $LblError.Text = "File $strScript not found"
    }
    
    return $bln
}

Il y a plusieurs méthodes pour exécuter un script. Ici nous avons choisi de lire le fichier SQL ligne par ligne et exécuter le résultat de notre lecture dès que nous rencontrons l'instruction SQL "GO".

C'est l'occasion de voir une expression régulière !
Rapidement, "^\s*GO\s*$" signifie que nous cherchons à identifier une chaîne de caractères contenant le mot GO entouré ou non d'espaces !

Comme il est toujours intéressant de savoir si notre script s'est bien exécuté, cette fonction retourne un booléen nous donnant cette information :)

Bref, nous avons tous les élements en main pour déployer notre base de données :

### <summary>
### Installs the SQL database.
### </summary>
function Install-Database()
{
    trap {
        Display-Error $_.Exception.Message
        if (($objSQL -ne $null-and
            ($objSQL.State -eq [Data.ConnectionState]::Open)) {
            $objSQL.Close()
        }
        $bln = $false
        continue
    }
    $LblError.Text = ''
    $objSQL = New-Object System.Data.SqlClient.SqlConnection
    $objSQL.ConnectionString = Generate-StringConnection 'master'
    Display-Info "Opening database connection..."
    $objSQL.Open()
    if ($objSQL.State -eq [Data.ConnectionState]::Open) {
        $strPath = Get-SQLScriptPath
        foreach ($strScript in $script:arrSQLGenerateDBScripts) {
            $bln = Execute-SQLScript $objSQL "$strPath\$strScript"
            if ($bln -eq $false) { break }
        }
        $objSQL.Close()
        Display-Info "Database connection closed"
        if ($bln) {
            Display-Info "Database successfully installed"
        } else {
            Display-Error "Error executing script $strPath\$strScript."
            Display-Info "Database installation aborted"
        }
    }
}

Nous sommes à la fin !

Cette fonction se sert donc de ce que nous avons vu avant et deploie notre base de données en plusieurs étapes :
  • Création de la chaîne de connexion à la base de données
  • Ouverture de la connexion à la base de données
  • Exécution des scripts SQL
  • Fermeture de la connexion à la base de données

Tout celà en affichant les étapes et les éventuelles erreurs dans notre fenêtre graphique !

Il ne nous reste plus qu'à déployer notre portail web en automatisant IIS ! Rendez-vous donc au prochain article !

Aucun commentaire:

Enregistrer un commentaire