jeudi 16 avril 2009

Automatisation de IIS

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

Après avoir déployé notre base de données sur laquelle s'appuiera notre portail Web, nous allons nous occuper du site Web.

En utilisant le deuxième menu...


... Nous découvrons notre interface graphique nous permettant de déployer notre site :


Là encore, l'interface est minimaliste. Les informations demandées sont :
  • Nom du serveur Web sur lequel déployer notre site,
  • Le port d'écoute,
  • Le chemin contenant les sources de notre site Web,
  • Un identifiant et un mot de passe qui seront utilisés pour le pool d'application.
Biensûr, les trois premiers champs sont pré-remplis.

Intéressons-nous au code PowerShell !

La première chose à faire est de vérifier que les informations renseignées sont correctes :

### <summary>
### Checks the Web part parameters.
### </summary>
### <return>True if parameters are correct, false otherwise</return>
function Check-WebParameters()
{
    $bln = $false
        
    $bln = ($TxtWebServer.Text.Length -gt 0-and
           ($TxtWebPort.Text.Length -gt 0-and
           ($TxtWebPath.Text.Length -gt 0-and
           ($TxtWebLogin.Text.Length -gt 0-and
           ($TxtWebPassword.Text.Length -gt 0)
    if ($bln) {
        $bln = ($TxtWebServer.Text -match "^\w[\w\d-_]*$"-or
               ($TxtWebServer.Text -match "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
        if ($bln -eq $false) {
            Display-error 'Specified server name is invalid'
        } else {
            $bln = ($TxtWebPort.Text -match "^\d{1,5}$")
            if ($bln -eq $false) {
                Display-error 'Specified port is invalid'
            } else {
                $bln = Test-Path $TxtWebPath.Text
                if ($bln -eq $false) {
                    Display-error 'Specified Web folder is invalid'
                } else {
                    $strLogin = $TxtWebLogin.Text
                    if ($strLogin.Contains('\')) {
                        $strLogin = $strLogin.Split('\')[1]
                    }
                    $objUser = SearchOne-InDomain '' "(samaccountname=$strLogin)"
                    if ($objUser -eq $null) {
                        Display-error 'Specified user doesn''t exist in the domain'
                        $bln = $false
                    }
                }
            }
        }
    } else {
        Display-error 'All fields are required'
    }
    
    return $bln
}

La fonction Check-WebParameters vérifie :
  • La validité du nom du serveur ou de l'adresse IP renseignée grâce à une expression régulière,
  • La validité du port (un nombre de 5 caractères maximum)
  • L'existence du compte utilisateur Active Directory renseigné. Cette dernière vérification est faite grâce à SearchOne-InDomain qui est une petite fonction dont voici le code :

    ### <summary>
    ### Searchs one object in an Active Directory container.
    ### </summary>
    ### <param name ="domainName">The container</param>
    ### <param name ="filter">LDAP filter</param>
    ### <param name ="scope">The scope for the research (optional)</param>
    ### <returns>Active Directory object</returns>
    function SearchOne-InDomain($container, $filter,
                                $scope = [DirectoryServices.SearchScope]::Subtree)
    {
        trap { return $null }
        $entry = New-Object DirectoryServices.DirectoryEntry("$container")
        $searcher = New-Object DirectoryServices.DirectorySearcher($entry)

        $searcher.Filter = $filter
        $searcher.SearchScope = $scope
        $searcher.FindOne()
    }

    Du déjà vu !
Bref une fois que nous avons vérifié que les informations renseignées sont correctes nous pouvons déployer notre site Web !

### <summary>
### Installs the IIS website.
### </summary>
function Deploy-WebServer()
{
    $bln = Check-WebParameters
    
    if ($bln -eq $false) { return }
    $bln = Copy-Source
    if ($bln -eq $false) { return }
    $LblError.Text = ''
    $strServer = $TxtWebServer.Text
    $strPath = $TxtWebPath.Text
    $strWebSiteName = 'Provisioning'
    $objLocator = New-Object -com WbemScripting.SWbemLocator
    $objProvider = $objLocator.ConnectServer($strServer, 'root/MicrosoftIISv2')
    $objService = $objProvider.Get("IIsWebService='W3SVC'")
    $objBindings = @($objProvider.Get('ServerBinding').SpawnInstance_())
    $objBindings[0].Properties_.Item('Port').value = $TxtWebPort.Text
    $createNewSiteMethod = $objService.Methods_.Item('CreateNewSite')

    $objInParameters = $createNewSiteMethod.InParameters.SpawnInstance_()
    $objInParameters.Properties_.Item('PathOfRootVirtualDir').value = $strPath
    $objInParameters.Properties_.Item('ServerBindings').value = $objBindings
    $objInParameters.Properties_.Item('ServerComment').value = $strWebSiteName
    
    Display-Info "Creating new WebSite '$strWebSiteName'..."
    $objOutParameters = $objService.ExecMethod_("CreateNewSite", $objInParameters)
    Display-Info "WebSite '$strWebSiteName' created"
    $id = ''
    $objOutParameters.properties_ | % {
        $id = $_.Value -match "[^']'([^']+)'.*"
        if ($id) { $id = $matches[1] }
    }
    if ($id.ToUpper() -match "^W3SVC/\d+$") {
        Display-Info "Creating new Application Pool '$strWebSiteName'..."
        $bln = Create-ApplicationPool $strServer $strWebSiteName
        if ($bln) {
            Display-Info "Configuring Website '$strWebSiteName'"
            $objSite = [ADSI]"IIS://$strServer/$id/Root"
            $objSite.Put("DefaultDoc", "Default.aspx")
            $objSite.Put("AppPoolId", $strWebSiteName)
            $objsite.put("AuthFlags", 4)
            $objsite.Put("AppFriendlyName", $strWebSiteName)
            $objsite.Put("AccessFlags", 1)
            $objsite.Put("AccessRead", $true)
            $objsite.Put("AccessScript", $true)
            $objsite.Put("AccessExecute", $true)
            $objSite.SetInfo()
            if ((Get-ChildItem env:COMPUTERNAME).Value -eq $strServer) {
                Set-FrameWorkVersion $id
                Display-Info "WebSite '$strWebSiteName' successfully created"
            } else {
                Display-Info "WebSite '$strWebSiteName' successfully created. " +
                             "You need to manually set up the .Net 2 version"
            }
        } else {
            Display-Error "Error creating Application pool '$strWebSiteName'"
            Display-Info "WebSite '$strWebSiteName' has not been configured"
        }
    } else {
        Display-Error "Invalid WebSite ID ($id)"
        Display-Info "WebSite '$strWebSiteName' has not been configured"
    }
}

La fonction Deploy-WebSite fait le travail ! Nous allons y jeter un petit coup d'oeil car c'est le plus intéressant !

En début de fonction, nous utilisons Copy-Source : c'est une petite fonction qui utilise la Cmdlet Copy-Item pour copier les sources de notre portail vers le dossier de notre futur site ; rien d'exceptionnel, alors nous continuons !

Ensuite, vous pouvez remarquez que nous attaquons notre serveur IIS en ADSI. Celà rend cette fonction utilisable à partir de la version 6 de IIS.

Dans cette fonction nous faisons donc les opérations suivantes :
  • Création du site Web avec un nom et un port d'écoute donné,
  • Création d'un pool d'application qu'utilisera notre site. Pour celà nous avons créé la fonction Create-ApplicationPool :

    ### <summary>
    ### Creates the IIS application pool.
    ### </summary>
    function Create-ApplicationPool($strServer, $strAppPoolName)
    {
        trap [Exception] {
            Display-Info $_.Exception.Message
            $bln = $false
            continue
        }
        $bln = $false
        $objApp = [ADSI]"IIS://$strServer/W3SVC/AppPools/$strAppPoolName"
        if ($objApp.distinguishedname -eq $null) {
            $strDomain = ([ADSI]'').Name.Value
            $objApp = [ADSI]"IIS://$strServer/W3SVC/AppPools"
            $objPool = $objapp.Create("IIsApplicationPool", $strAppPoolName)
            $objPool.Put('AppPoolIdentityType', 3)
            $strLogin = $TxtWebLogin.Text
            if ($strLogin.Contains('\'-eq $false) {
                $TxtWebLogin.Text = "$strDomain\$strLogin"
                $TxtWebLogin.Refresh()
            } else {
                $strLogin = $strLogin.Split('\')[1]
            }
            Display-Info "Setting Application pool credentials to $strLogin..."
            $objPool.Put('WAMUserName', $TxtWebLogin.Text)
            $objPool.Put('WAMUserPass', $TxtWebPassword.Text)
            $objPool.SetInfo()
            Display-Info "Adding user $strLogin to group IIS_WPG..."
            $objGroup = [ADSI]"WinNT://$strServer/IIS_WPG"
            $objGroup.Add("WinNT://$strDomain/$strLogin")
            $bln = $true
        } else {
            Display-Info "Application pool '$strAppPoolName' already exists."
            $bln = $true
        }
        
        return $bln
    }

    Cette fonction ajoute également le compte utilisé par le pool d'application dans le groupe local IIS_WPG.
  • Configuration du site Web :
    • Page par défaut paramétrée à la page Default.aspx
    • Utilisation du pool d'application précédemment créé
    • Paramétrage de l'authentification Windows
    • Mise en place des droits de lecture et d'exécution
  • Utilisation de la version 2 du framework .NET. Cette étape n'est faite que si notre Solution Accelerator est lancé depuis le serveur Web. La fonction Set-FrameworkVersion configure notre site web pour qu'il utilise la version 2 :

    ### <summary>
    ### Sets the .NET framework version for a given website.
    ### </summary>
    ### <param name="strID">Website ID</param>
    function Set-FrameWorkVersion($strID)
    {
        $strPath = (Get-ChildItem Env:windir).Value + '\Microsoft.NET\Framework\'
        
        $objDir = Get-ChildItem $strPath | where {
                   ($_.GetType().ToString() -eq 'system.io.directoryinfo'-And
                   ($_.name -match "^(v2.[\d\.]+)$")} | sort name
        if ($objDir -ne $null) {
            $strPath += $matches[1]
            $strPath += "\aspnet_regiis.exe"
            $objSI = New-Object System.Diagnostics.ProcessStartInfo
            $objSI.UseShellExecute = $false
            $objSI.FileName = $strPath
            $objSI.Arguments = "-s $strID/Root"
            $objSI.RedirectStandardOutput = $true
            $objP = [System.Diagnostics.Process]::Start($objSI)
            $objP.WaitForExit()
        }
    }

    Vous pouvez remarquer que nous exécutons le binaire aspnet_regiis.exe !
Biensûr, les erreurs sont affichées en bas de notre fenêtre si elles surviennent !

Nous arrivons à la fin de l'automatisation de IIS... J'espère que ça n'a pas été trop long !

1 commentaire:

  1. Très sérieux tout ça, merci pour le travail accompli. Je serai preneur pour quelques paramètres que je n'arrive pas à positionner sur le site. Spécification Filtre ISAPI (je l'ai fait en script que je lance via un .Bat) mais en Powershell, ce serait sympa. Décocher Désactivation Activer l'enregistrement dans le journal. Et positionner des Host Headers dans le site IIS. Ca fait beaucoup de choses. Merci encore.

    RépondreSupprimer