Creating Multiple Azure Virtual Machines – Part Vier

In my last post, Creating Multiple Azure Virtual Machines – Part Tre, I demonstrated the various ways that you could execute commands using script blocks on the remote server. If you don’t understand this topic thoroughly then please go back and re-read this post, as it is the backbone of everything we will do going forward. If you are still here then let’s continue our trek through the world of PowerShell and start moving on to doing specific things. In this post I will talk about how to setup the virtual machine so that it is ready for the deployment of the software. This post will cover getting all of the pre-requisites installed and configured on the system.

Setup File Access Rights

As promised, the first thing that we have to do after creating all of the deployment folders on the remote machine is setup the file access rights for them. Here is the script block that I’m going to use to accomplish this task.

$setFileAccessRightsOnFolder = {
        Param([string] $folderName, [string] $identity, [string] $fileSystemRights, [ValidateSet('Allow', 'Deny')] [string] $type = 'Allow')

        $folderInfo = Get-item -Path $folderName
        if ($folderInfo.Exists -eq $false) {
            throw ('The folder "' + $folderName + '" does not exist!  Unable to set the security rights on non-existant folder')
        }

        $folder = Get-Item -Path $folderName
        $securityDescriptor = Get-Acl $folder

        $fileAccessRule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $identity, $fileSystemRights, 'ContainerInherit, ObjectInherit', 'None', $type

        $securityDescriptor.SetAccessRule($fileAccessRule)
        $folder.SetAccessControl($securityDescriptor)

        Write-Host (' -> Security rights set on folder "' + $folderName + '"')
    }

This must be executed in order to setup the file system rights on the remote machine. As a matter of fact we can do this in the same call as creating the folders as described earlier.

Write-Host 'Creating all folders required for deployment...'
Invoke-Command -Session $remoteSession -ScriptBlock { 
            New-Item -ItemType directory -Path 'C:\XYZ_Widgets'            
            New-Item -ItemType directory -Path 'C:\XYZ_Widgets\Application'
            New-Item -ItemType directory -Path 'C:\XYZ_Widgets\Data'

            Write-Host ' -> Giving "Everyone" full control on the newly created folder structure'
            Invoke-Command -Session $remoteSession -ScriptBlock $setFileAccessRightsOnFolder -ArgumentList 'C:\XYZ_Widgets', 'Everyone', 'FullControl'

            Write-Host ' -> Folders created.'

        }

WCF Features

If you are creating a .NET client server application it is more than likely that you will have at least at some point used the Windows Communication Foundation (WCF) in your application. In order for your application to run correctly (at least with IIS) you need to make sure that this information is installed on the system. Let’s take a look at how this is done

function InstallWCFFeatures {
    Param([System.Management.Automation.Runspaces.PSSession] $remoteSession)

    Write-Host 'Installing All WCF 4.5 Services and Features...'

    $installResult = Invoke-Command -Session $remoteSession -ScriptBlock { Install-WindowsFeature NET-WCF-Services45, NET-WCF-HTTP-Activation45, NET-WCF-TCP-Activation45, NET-WCF-TCP-PortSharing45 } 
    if ($installResult.Success -eq $false) {
        throw 'INSTALL FAILURE: Unable to install the WCF 4.5 Services and Features.'
    }
    
    Write-Host ' -> WCF Services installed successfully.'
    Write-Host (' -> Restart Needed: ' + $installResult.RestartNeeded.ToString())
}

While I didn’t do it here, you could use the $installResult.RestartNeeded as a return value from this method to determine if you need to restart the virtual machine to complete the installation. This should look familiar to you. We installed IIS in the same manner in the post Creating Multiple Azure Virtual Machines – Part Deux

Setup HTTP Binding

Since we are dealing with multiple Azure Virtual Machines, we need to consider that one port just isn’t enough. We need to make each virtual machine listen on its own HTTP port. Do you remember in the first post, Creating Multiple Azure Virtual Machines, where we setup the end points to allow the virtual machines to be accessed by the outside world? Well we need to tell the internals of each machine where to bind the HTTP port to.

$setHttpBindingForSite = {
    Param([string] $siteName, [int] $portNumber)

    Write-Host ('Updating the HTTP binding for the "' + $siteName + '" site')

    [string] $protocolName = 'http'
    $existingBinding = (Get-WebBinding | Where-Object { $_.protocol -eq $protocolName })
    if ($existingBinding -ne $null) {
        Write-Host ' -> Removing the existing binding'
        Remove-WebBinding -Name $siteName -Protocol $protocolName
    }

    New-WebBinding -Name $siteName -Protocol $protocolName -Port $portNumber -Force

    Write-Host 'Binding Updated.'
}

function SetHttpBinding {
    Param([Parameter(Mandatory = $true)] 
          [System.Management.Automation.Runspaces.PSSession] $remoteSession,
          [Parameter(Mandatory = $true, HelpMessage="(Required) The http port that you want to assign to the site.`n")] 
          [int] $httpPort,
          [Parameter(Mandatory = $false)]
          [string] $siteName = 'Default Web Site')

    ValidateRemoteSession $remoteSession

    Invoke-Command -Session $remoteSession -ScriptBlock $setHttpBindingForSite -ArgumentList $siteName, $httpPort
}

Using this functionality, I can loop over my virtual machines setting up the HTTP binding on each machine.

Invoke-Command -Session $remoteSession -ScriptBlock {
            Param([int] $port)

            Remove-WebBinding -Name 'Default Web Site' -Protocol 'http'
            New-WebBinding -Name 'Default Web Site' -Protocol 'http' -Port $port -Force
            New-NetFirewallRule -DisplayName 'HTTP' -Direction Inbound -Protocol TCP -LocalPort $port
        } -ArgumentList $httpPort

As you can see I’ve performed all of my IIS configuration. Now it’s time to move on to the FTP server.

FTP Server

In order to push some of the software to the remote server, I needed an FTP server on the virtual machine. I can use these functions to ensure that the FTP server is installed on the remote machine as well as configure the FTP server itself.

function EnsureFtpServerInstalled {
    Param([Parameter(Mandatory = $true)] [System.Management.Automation.Runspaces.PSSession] $remoteSession)

    ValidateRemoteSession $remoteSession

    Write-Host "Checking for the existence of IIS Web Server"

    $ftpServerResults = Invoke-Command -Session $remoteSession -ScriptBlock { Get-WindowsFeature Web-Ftp-Server }
    if ($ftpServerResults.Installed -eq $false) {
        Write-Host "The FTP Server Feature was not found on the machine, installing..."
        Invoke-Command -Session $remoteSession -ScriptBlock { Install-WindowsFeature Web-Ftp-Server -IncludeAllSubFeature }        
    }
}

function CreateFtpSite {
    Param([Parameter(Mandatory = $true)] 
          [System.Management.Automation.Runspaces.PSSession] $remoteSession,
          [Parameter(Mandatory = $false, HelpMessage="(Required) The port number to create the FTP public port to use`n")] 
          [int] $ftpPortNumber = 21)

    ValidateRemoteSession $remoteSession

    $ftpSiteName = 'Deployment FTP Site'
    
    Write-Host 'Creating the FTP site'

    Invoke-Command -Session $remoteSession -ScriptBlock { 
            Param([string] $siteName, [int] $port)
            New-WebFtpSite -Name $siteName -Port $port -PhysicalPath 'C:\XYZ_Widgets\Source' 
        } -ArgumentList $ftpSiteName, $ftpPortNumber

    Invoke-Command -Session $remoteSession -ScriptBlock { 
            Param([string] $siteName)
            Set-ItemProperty ('IIS:\Sites\' + $siteName) -Name ftpServer.security.authentication.anonymousAuthentication.enabled -Value $true 
        } -ArgumentList $ftpSiteName

    Invoke-Command -Session $remoteSession -ScriptBlock { 
            Param([string] $siteName)
            Set-ItemProperty ('IIS:\Sites\' + $siteName) -Name ftpServer.security.ssl.controlChannelPolicy -Value 0 
        } -ArgumentList $ftpSiteName

    Invoke-Command -Session $remoteSession -ScriptBlock { 
            Param([string] $siteName)
            Set-ItemProperty ('IIS:\Sites\' + $siteName) -Name ftpServer.security.ssl.dataChannelPolicy -Value 0 
        } -ArgumentList $ftpSiteName

    Invoke-Command -Session $remoteSession -ScriptBlock { 
            Param([string] $siteName)
            Import-Module WebAdministration
            Add-WebConfiguration -Filter /System.FtpServer/Security/Authorization -Value (@{AccessType=”Allow”; Users=”?”; Permissions=”Read, Write”}) -PSPath IIS: -Location $siteName
        } -ArgumentList $ftpSiteName

    Write-Host ('Created the FTP Site "' + $ftpSiteName + '"')

    Invoke-Command -Session $remoteSession -ScriptBlock {
            Param([int] $port)
            $ruleDisplayName = ('FTP Server (Port ' + $port.ToString() + ')')
            New-NetFirewallRule -DisplayName $ruleDisplayName -Direction Inbound -Protocol TCP -LocalPort $port
        } -ArgumentList $ftpPortNumber

    Write-Host ('Added Rule FTP Server (TCP on port ' + $ftpPortNumber +') in Windows Firewall')
}

# ----------------[ Example ]---
# EnsureFtpServerInstalled -remoteSession $rs
# CreateFtpSite -remoteSession $rs -ftpPortNumber 21

Conclusion

Now that all of our pre-requisite software are installed, we are ready to start manipulating the system. That will include a configuration for the Web Server to host our application. That however, will be a post for another time. Until next time, Auf Weidersehen!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s