Application Management – Challenges in Service Templates, Dynamic Connection String and NLB DNS entry

This blog is actually derived from my earlier post about creating a Service Template for DotNetNuke, read about it here . I decided to separate this content from that post for 2 reasons, first is that it would have been too long, next is that this information can simply help any service templates using NLB and web.config connection strings for a SQL server deployed in the template. Note: these workarounds are not configured in the DotNetNuke template although the scripts are included in that download found here for your customization.

During this deployment we find 2 real world challenges arise which have multiple workarounds that depend on a number of things related to your use case and your environment. The first is changing the web.config files connection string to the database dynamically based on the SQL server’s name that is created as part of the deployment. The easiest workaround is to ask the user to enter a name for the SQL server during deployment and use that name in a script or as a parameter to modify the config file. This of course is not very dynamic and also not very smart as it doesn’t check DNS and confirm that the name is not already in use. Maybe if we use a Service request and/or Orchestrator to automate deployment we could manage the entries and test against DNS and other corporate standards. For my purposes I accepted the default name, which looks like ‘servicevm#####’. I could have even created a naming convention like SQLPROD####.

Challenge number 2 is that there is no entry in DNS at the end of the service deployment which points to the network load balancers IP address. The problem is that there is no command or PowerShell that can run as a pre or post install script to accomplish creating this entry.

These are essentially what can be called out of band template modifications as the main challenge is you cannot access some of this information via scripts until the template is completely deployed. So in my normal style of first over complicating things before I simply them I created this workaround. In my workaround I essentially created a PowerShell script that creates a Scheduled Task in the web tier VM. In this  CreateCustomScheduledTask.ps1   PowerShell script I create 4 Key Value Pair (KVP) entries that correspond to some required variables like VMM Server name and Service Template ID of the current service. I also create a KVP which tracks the result of the scheduled task, true or false. Using this I can control clean-up of the scheduled task or simply do nothing if successful in the past. To create the scheduled task I use:

$cmd = "Schtasks.exe /Create /TN ServiceTemplatedTask /XML " + $destDirectory + "ST.xml /RU " + $ST_User + " /RP " + $ST_Password

In the above command the ST.xml file is the exported scheduled task I created during testing the task and simply exporting it from the Task Scheduler. I do have to modify the Actions node of the XML before importing with the correct command arguments to use for the scheduled task. I do this by running the following PowerShell in the CreateCustomScheduledTask.ps1   file:

$xml = New-Object XML
$xml.Load("$destDirectoryST.xml")
$xml.Task.Actions.Exec.Arguments = "-command $destDirectory"+"DynamicConnectStringConfig.ps1"
$xml.Save("$destDirectoryST.xml")

In this case the arguments are the PowerShell script to run and the directory to find it. I have also broken every rule I can think of but I have included some hard coded variables in the script which include a user name and password in clear text which will be used as the run as account for the scheduled task. The scheduled task for dynamic connection string configuration requires the ability to remote into the VMM server so it will need to run under credentials that have access to do so. Sure there are other ways, I cheated so I could complete the project on time. Feel free to comment on better options or other choices you make. You can also see I hard coded the script name in the above snippet which should be a variable and/or argument as well.

Here is the full script I used:
$folder = $args[0] # Derived from the @NameOfIISSite@ parameter in the service template
$vmmServer = $args[1] # Virtual machine manager name
$serviceID = $args[2] # Derived from the known service ID parameter @serviceID@
$destDirectory = $env:ProgramData + "CustomScripts"
$regPath = "HKLM:SOFTWAREMicrosoftVirtual MachineServiceTemplate"
$ST_User = "contoso!installer"
$ST_Password = Pass@word1

try{
    # Creates registry key if it doesn;t already exist. This is where we track if this script completes.

    If (!(Test-Path $regPath))
    {
        New-Item $regPath | Out-Null
    }
        Set-ItemProperty -Path $regPath -Name 'NameOfIISSite' -Value $folder
        Set-ItemProperty -Path $regPath -Name 'vmmServer' -Value $vmmServer
        Set-ItemProperty -Path $regPath -Name 'serviceID' -Value $serviceID
        Set-ItemProperty -Path $regPath -Name 'DynamicConnectResult' -Value 'False'

if ((Test-Path -path $destDirectory) -ne $True){ New-Item $destDirectory -type directory}

$sourceDirecotry = Split-Path -Path $MyInvocation.MyCommand.Definition
$files = Get-ChildItem -Path:$sourceDirecotry

foreach($file in $files)
{
    $path = $file.FullName
    Copy-Item -Path:$path -Destination:$destDirectory -Force -Confirm:$false
}

$xml = New-Object XML
$xml.Load("$destDirectoryST.xml")
$xml.Task.Actions.Exec.Arguments = "-command $destDirectory"+"DynamicConnectStringConfig.ps1"
$xml.Save("$destDirectoryST.xml")

$cmd = "Schtasks.exe /Create /TN ServiceTemplatedTask /XML " + $destDirectory + "ST.xml /RU " + $ST_User + " /RP " + $ST_Password
$result = invoke-expression "cmd.exe /c `"`"$cmd 2>&1`"`""

        # Writing an event
        $EventLog = New-Object System.Diagnostics.EventLog('Application')
        $EventLog.MachineName = "."
        $EventLog.Source = "Create Scheduled Task"
        $EventLog.WriteEntry("$result","Information", "1000")

 

} Catch [Exception]{
        $EventLog = New-Object System.Diagnostics.EventLog('Application')
        $EventLog.MachineName = "."
        $EventLog.Source = "Create Scheduled Task"
        $EventLog.WriteEntry("Failed to create scheduled task for service template deployment. The error message: $_.Exception.Message POSH Error: $error","Error", "1000")
        }

Dynamic Web.Config connectionstring

For dynamically modifying web.config connection strings the script needs to talk to Virtual Machine Manager so in my lab that means I need to add a service account I’ll use later to the WinRMRemotingWMIUsers_ local group on VMM. I did that by running the following in PowerShell as Administrator:

Enter-PSSession -ComputerName vmm01
net localgroup WinRMRemoteWMIUsers__ /add contoso!installer

I then added that service account to VVM Administrators Role by running the following commands:

Import-Module-Namevirtualmachinemanager
$userRole = Get-SCUserRole -Name "Administrator"
Set-SCUserRole -UserRole $userRole -Description "Administrator User Role" -JobGroup "d65c8a6e-fe0f-460c-a789-95a4bc9712ec" -AddMember @("CONTOSO!installer")

I have already created a run as account in VMM for my CONTOSO!installer service account, if not do that now. We also have 2 hard coded variables here that can be parameters submitted at run time as arguments are stored elsewhere. The first is what will the machine tire name be for the SQL server we want to get it name for. The second is the location we use for Key Value Pairs (KVP).

# Parameters to be modified to reflect variables from service template
$tierName = "SQL2012-SP1 - Machine Tier 1"
$regPath = "HKLM:SOFTWAREMicrosoftVirtual MachineServiceTemplate"

Then get the registry entries from earlier and using the KVP that determines if the task completed or not we will test and if false, which it is by default, we run the logic that gets SQL’s name and modifies the config file as needed. If it works we change the KVP to false and for testing purposes instead of deleting the scheduled task we disable it.

  if($result -eq 'False')
  &nbsp