Wednesday, May 27, 2009

IIS automation

Previous part of this post is HERE !

After having deployed the SQL database on which our Web portal is based on, we are going to deploy the web site... Using the second menu:


... We are discovering the GUI dedicated to the Web site deployment:


As the previous one, GUI is very simple. Four information are needed to deploy the Web Site:
  • Web server name,
  • Listening port,
  • Path to the Web Site source,
  • Credentials used by the application pool.
First fields are automatically filled and can be modifiable.

Let's see the PowerShell code !

First, we need to check that all specified information are correct:

### <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
}

Check-WebParameters function checks:
  • Web server name or IP address using a regular expression,
  • Listening port (a number up to 5 digits),
  • Existence of the user account used by the application pool. SearchOne-InDomain function makes the job:

    ### <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()
    }

    We have already seen all this stuff !
Well, after having checked all information we can deploy our Web Site !

### <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"
    }
}

Deploy-WebSite function makes the job!

At the begining of the function, we used Copy-Source: it's a small function called to copy Web Site source files to the Web server destination folder. This function uses Copy-Item Cmdlet (nothing special!).

Then, we use ADSI to configure IIS! So, we can use this function to deploy a Web Site using IIS 6 and later version!

The function makes the following operations:
  • Web Site creation using a specific name and listening port,
  • Application pool creation using Create-ApplicationPool function:

    ### <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
    }

    This function also adds the specified user account used by the application pool to the local group IIS_WPG
  • Web Site configuration:
    • Dafault web page configured to "Default.aspx"
    • Application pool assignment
    • Windows authentication configuration
    • Permisssions configuration: Read and Execute permissions
  • .Net Framework version configuration. This step is available only if the machine running our Solution Accelerator is our Web server. Set-FrameworkVersion function configures the Web Site to use the version 2 of the .NET framework:

    ### <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()
        }
    }

    aspnet_regiis.exe binary is executed here!
All warnings or errors are displayed at the bottom of the window!

Well, IIS automation is simple! I hope that was not boring :)

No comments:

Post a Comment