Hello once again!

So, here we are, Part 2 of The New World of Tenant Provisioning with Windows Azure Pack blog series (find Part 1: Intro & TOC here). Finally, some PowerShell/SMA Runbook examples!


Automated Deployment of Tenant Network and Identity Workload

What does that mean, exactly?

Well for the context of this blog series, it means I am going to provide the PowerShell/SMA Runbook scripts necessary to create a Tenant Virtual Network (Isolated Software Defined Network (SDN)) & Active Directory VM Role. So, for organization’s sake, this blog post will be split into two main sections – one for automated Tenant Virtual Network deployment, and one for automated VM Role deployment.

Automated Tenant Virtual Network Deployment

For the most part, the PowerShell/SMA Runbook example for this already exists. Granted, you would have to be a Building Clouds Blog super-fan to know exactly where it is, but it does exist on this very blog. That said, for this series, we want to promote it much more, and underline the significance of it in this example solution.

So where did this example live before being reestablished here?

In this blog post: Automation–PowerShell Workflow Script Spotlight–Deploying Virtual Machine Manager Service Templates “OnBehalfOf” Tenant Administrator User Roles

An admittedly, under-promoted yet valuable blog post on the automation of various VMM resources via PowerShell workflow, from the Service Administrator “OnBehalfOf” the Tenant Administrator.


In fact, I believe now is a great time to take a moment and describe the “Scope of Management” for these two personas in an image:

image

The reason I believe this is important, is that I will be referring to each persona as this blog series continues. Now, this is not a comprehensive list of all the potential areas each persona has management over, but it covers what we need here.

Note     There may be other uses or ways to access the WAP Tenant API (non-Public) than just as a Service Administrator. From what I have seen, it requires bearer token authorization. And since the best way to get this token is via the WAP Admin PowerShell Cmdlet (Get-MgmtSvcToken), I made some assumptions.


Okay, let’s knock this Tenant Virtual Network stuff out…

Create a VM Network “OnBehalfOf” a User Role

The following PowerShell workflow script (Create-VMNetwork) will create a VMM VM Network with the following settings (leveraging VMM PowerShell Commands):

  • VM Network Name: <VM Network Name generated by Owner User Role Name defined with parameter>
  • Subnet Name: <defined in script: “TenantSubnet”>
  • Subnet Value: <defined in script: “192.168.0.0/24”>
  • IP Address Pool Name: <defined in script: “TenantIPPool”>
  • IP Address Range Start: <defined in script: “192.168.0.100”>
  • IP Address Range End: <defined in script: “192.168.0.199” – providing for 100 available addresses>
  • DNS IP: <defined in script: “192.168.0.100” – first IP in Pool>
  • OnBehalfOfUser: <User Name parsed from Owner User Role Name defined with parameter>
  • OnBehalfOfUserRole: <User Role parsed from Owner User Role Name defined with parameter>

Note     You may keep these example settings, or modify to fit your deployment specifications.

Example PowerShell workflow script for Create-VMNetwork

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
workflow Create-VMNetwork
{
    param
    (
    [string]$OwnerUserRole,
    [string]$VmmServerName,
    [string]$CloudName,
    [string]$LogicalNetworkName
    )
 
    inlinescript
    {
        $subnetValue = "192.168.0.0/24"
        $subnetName = "TenantSubnet"
        $dnsIP = "192.168.0.100"
        $ipAdressPoolName = "TenantIPPool"
        $ipAddressRangeStart = "192.168.0.100"
        $ipAddressRangeEnd = "192.168.0.199"
        $UserRole = $Using:OwnerUserRole
        $User = $UserRole.Split("_")[0]
        $vmNetworkName = "Tenant Network ($User)"
       
        Get-SCVMMServer -ComputerName $Using:VmmServerName -ForOnBehalfOf | Out-Null
        $OwnerUserRoleObj = Get-SCUserRole | where {$_.Name -match $Using:OwnerUserRole}

        $VMNetwork = Get-SCVMNetwork -OnBehalfOfUser $User -OnBehalfOfUserRole $OwnerUserRoleObj
        if(!$VMNetwork) {
            $CloudObj = Get-SCCloud -Name $Using:CloudName
            $logicalNetwork = Get-SCLogicalNetwork -Cloud $CloudObj -Name $Using:LogicalNetworkName
            $vmNetwork = New-SCVMNetwork -Name $vmNetworkName -LogicalNetwork $logicalNetwork `
                -OnBehalfOfUser $User -OnBehalfOfUserRole $OwnerUserRoleObj
       
            $subnet = New-SCSubnetVLan -Subnet $subnetValue 
            $vmSubnet = New-SCVMSubnet -Name $subnetName -VMNetwork $vmNetwork -SubnetVLan $subnet `
                -OnBehalfOfUser $User -OnBehalfOfUserRole $OwnerUserRoleObj

            $allDnsServer = @($dnsIP)

            $staticIPAddressPool = New-SCStaticIPAddressPool -Name $ipAdressPoolName `
                -VMSubnet $vmSubnet -Subnet $subnetValue -IPAddressRangeStart $ipAddressRangeStart `
                -IPAddressRangeEnd $ipAddressRangeEnd -DNSServer $allDnsServer `
                -RunAsynchronously -OnBehalfOfUser $User -OnBehalfOfUserRole $OwnerUserRoleObj
        }
    }
}

Note     In general, the Create-VMNetwork workflow gets called once per Tenant Admin (Owner User Role, which is the equivalent of User + Plan Subscription). In fact, this workflow is most often called as part of the SMA Runbook linked to the Subscription.Create event within WAP/SPF, meaning that as soon as a Tenant Admin User Subscribes to the related Plan, the Tenant Virtual Network is created automatically for that Subscription. For more information (and a specific example) about how WAP leverages SPF events to initiate SMA Runbooks, see the following TechNet Article: Using automation with Virtual Machine Clouds and blog post: Automation–Monitoring and Notifying in Windows Azure Pack with SMA

Configuring more than just the basics…

For instance, what if you wanted to configure NAT with a specific Gateway Device and External IP Address Pool?

Well, add the following PowerShell to the above example script:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015

#NAT, Gateway and External IP Address Pool Variables
$vmGWServiceName = "Gateway Service Name"
$vmNetworkGwName = "{0}_Gateway" -f 
$vmNetworkName
$vmExtStaticIPAddyPoolName
 = "External IP Address Pool Name"
$vmNetworkNATConnName = "{0}_NatConnection" -f $vmNetworkName

#NAT, Gateway and External IP Address Pool Commands
$gatewayDevice = Get-SCNetworkGateway -Name 
$vmGWServiceName
$VmNetworkGateway
 = Add-SCVMNetworkGateway -Name $vmNetworkGwName -EnableBGP $false `
    -NetworkGateway $gatewayDevice -VMNetwork $vmNetwork `
    -OnBehalfOfUser $User -OnBehalfOfUserRole 
$OwnerUserRoleObj
$externalIpPoolVar
 = Get-SCStaticIPAddressPool -Name 
$vmExtStaticIPAddyPoolName
$natConnection
 = Add-SCNATConnection -Name $vmNetworkNATConnName `
    -VMNetworkGateway $VmNetworkGateway -ExternalIPPool $externalIpPoolVar `
    -OnBehalfOfUser $User -OnBehalfOfUserRole $OwnerUserRoleObj

Note     Many other options are available, these are just the most common for a given scenario. The simple way to keep adding to this script is to make modifications in VMM, capture the generated script(s) and work the new portions in to the existing script.

Also Note     While the Add-SCVMNetworkGateway and Add-SCNATConnection commands do allow for -OnBehalfOfUser and -OnBehalfOfUserRole the user and user role specified may not have the necessary permissions to complete the operation (likely to the Gateway). The command execution may fail with a “You do not have permission to access one or more of the objects required by this operation.” error message. In this case, you may want to consider granting that access, or forgoing the “OnBehalfOf” for these two commands.

Calling the Create-VMNetwork PowerShell workflow

The following is a very basic example for calling this workflow:

001
002
003
004
005
006
$VMMServer = "MY_VMM_SERVER" 
$UserRole = "USER_ROLE" 
$CloudName = "My Tenant Cloud" 
$LogicalNetworkName = "Contoso Logical Network" 

Create-VMNetwork -VmmServerName $VMMServer -OwnerUserRoleName $UserRole.Name -CloudName $CloudName -LogicalNetworkName $LogicalNetworkName

Again, there are lots of options here, choose the one that makes sense for your deployment. And I am not going to dive into the details for this example, but obviously you can leverage alternate objects/variables to collect/pass the parameter data within this call (as it is a 1:1 Tenant Admin User Role:VMNetwork in this example where Tenant Admin User Role = User + Subscription to a Plan).


Why go directly against VMM, as opposed to leveraging the WAP Tenant API?

For two reasons, really – First, I wanted to highlight and leverage existing known-good and well-used scripts; Second, the only available and published documentation / examples on the VM Network API are written in C#, not PowerShell.
This documentation can be found here: http://msdn.microsoft.com/en-us/library/dn765986.aspx.

So, the above script is what we have been using on my team in our Demo/Test/Dev environment for months now.

Also, one might argue that Network belongs to Fabric Management, which in turn belongs in VMM. Either way, based on what I could find, VMM is your [current] best bet.

Note    All this being said, I do have confirmation that this API does exist, and as soon as I have a public link for the API Reference for creating Virtual Networks in WAP, I will update this post right here.


Automated Active Directory VM Role Deployment

Let’s break this down…

The Options

In fact, this is another great time to illustrate this in image form, for these two personas:

image

Note     These options are the same for any Automated VM Role Deployment, Active Directory happens to be the first one to be deployed, so it gets all the attention.


The Process

Before we get started on the process, now is the perfect time to give a shout out to one of my WSSC CAT teammates: Nader Benmessaoud [MSFT], for whom I would like to thank for paving the way towards my understanding this process, with early work against the Public Tenant  API, PowerShell examples, and foundational support in this effort. That said, I believe it is important for everyone to understand the current step-by-step process necessary to automatically deploy a VM Role via PowerShell against the available endpoints. It will let you appreciate the script that much more.

  1. Generate the Gallery Item VM Role Reference URI (based on Subscription ID and Tenant Portal Address)
  2. Invoke-WebRequest to Get the Gallery Item VM Role Reference (portion of the Gallery Item VM Role Resource Definition (ResDef) URI specific to the Gallery Item VM Role, based on the Gallery Item VM Role Name and data returned from Step #1)
  3. Generate the Gallery Item VM Role ResDef URI (based on data returned from Step #2)
  4. Invoke-WebRequest to Get the Gallery Item VM Role ResDef (based on the URI from Step #3, data returned in JSON)
  5. Convert (Deserialize) the returned ResDef JSON to a 'System.Collections.Generic.Dictionary[String,Object]
  6. Create the Gallery Item VM Role Parameter Hashtable (based on custom variable data)
  7. Convert the Gallery Item VM Role Parameter Hashtable to JSON
  8. Create the Gallery Item VM Role Resource Definition Configuration (ResDefConfig) 'System.Collections.Generic.Dictionary[String,Object]' (based on converted Gallery Item VM Role Parameter data (JSON) and Version information)
  9. Create the Gallery Item VM Role Payload Hashtable (based on custom variable data, Gallery Item VM Role ResDef and ResDefConfig Dictionary Objects)
  10. Convert the Gallery Item VM Role Payload Hashtable to JSON
  11. Verify/Create Cloud Service Name (based on custom variable data)
  12. Generate the Gallery Item VM Role Deployment URI (based on Subscription ID, Tenant Portal Address, and Cloud Service Name)
  13. Invoke-WebRequest to Post the Gallery Item VM Role Payload JSON (based on the URI from Step #14)

Step #11 (Cloud Service Creation/Verification) has several Sub-Steps:

  1. Generate the Cloud Service URI (based on Subscription ID and Tenant Portal Address)
  2. Invoke-WebRequest to Get the Cloud Service Data and verify if Cloud Service already exists (based on the URI from Sub-Step #1)
  3. If exists, Output Cloud Service Name
  4. If notexists, Create the Cloud Service Parameter Hashtable (based on custom variable data)
  5. Convert the Cloud Service Parameter Hashtable to JSON
  6. Invoke-WebRequest to Post the Cloud Service JSON (based on the URI from Sub-Step #1)
  7. Once created, Output Cloud Service Name

Some people like images better, so here is one that represents the text above:

image


So, if you were counting, that is…

  • 4 URIs Dynamically Generated
  • 3 Invoke-WebRequest GETs (for ResDef information)
  • 4 Conversions (1 from JSON, 3 to JSON)
  • 3 Hashtables Created
  • 2 'System.Collections.Generic.Dictionary[String,Object]'
  • 1 Cloud Service Creation (or reference to an existing Cloud Service)
  • 2 Invoke-WebRequest POSTs (1 for the Cloud Service Creation, 1 for the VM Role Deployment)

The magic here really exists in handling the data from JSON to Dictionary, Hashtable to JSON, etc. In fact, as you will see in the final script, JSON is the preferred method for data storage and transfer between sections of the script (from InlineScript to InlineScript and workflow to workflow).

As a reference, be sure to review the following TechNet Article: Windows Azure Pack VM Roles Tenant API


Even More Magic

I realize I keep using this word to emphasize technical significance, but when I find something so useful and so “simple”, I label it as such. That said, there is a CRITICAL piece in this process that HAS to be pointed out specifically:

Get and Usage of the WAP MgmtSvcToken

I mention it in passing above, but this really makes everything work from a “Service Administrator leveraging the WAP Tenant API (non-public)” perspective.

Here is the TechNet Library Article on the WAP command used to retrieve the token: Get-MgmtSvcToken

Here is the snippet of script (you will see it within the larger script below) where the token is retrieved from WAP, and then placed in a $Headers Hashtable, along with the identity of the User (essentially the WAP Tenant API’s version of “OnBehalfOf”):

001
002
003
004
005
006
007
008
009
$AdminURI = "https://" + $Using:WAPServer + ":30004"
$AuthSite = "https://" + $Using:WAPServer + ":30072"
$ClientRealm = "http://azureservices/AdminSite"
$token = Get-MgmtSvcToken -Type Windows -AuthenticationSite $AuthSite `
    -ClientRealm $ClientRealm -DisableCertificateValidation

$Headers = @{
    Authorization = "Bearer $token"
    "x-ms-principal-id" = $Using:UserID }

Note     This $Headers Hashtable gets reused over and over, each time an Invoke-WebRequest is made against the WAP Tenant API (non-Public). In fact, this bit of script is the only reason the InlineScript is leveraged, remoting to the WAP Admin Server – the rest of the calls are 100% Invoke-WebRequests and do not require the WAP Cmdlet. It is also important to note, this functionality is not restricted to VM Clouds, but can be leveraged for WAP Tenant API (non-public) calls, in general.


The Scripts

Based on the various options listed above, and considering the already tremendous length of this blog post, I am going to choose a [hopefully] winning combination that will satisfy the masses. Here goes…

Colonel Mustard In The Study (With A Candlestick)

Oh wait - wrong combination!

Service Administrator in SMA (with the WAP Tenant API Only)

That’s better!

So, what does this mean?

  • Service Administrator – The persona the script will be executed from and written for
  • in SMA – Service Management Automation, the PowerShell workflow engine in WAP (the very same thing that will automatically Create a VM Network “OnBehalfOf” a User Role, as described above)
  • (with the WAP Tenant API Only) – Because the script is executed from the Service Admin persona, there are a couple options, one is a mix of WAP Tenant API and VMM Cmdlet calls, the other is purely WAP Tenant API calls (no direct VMM commands used). Going with the WAP Tenant API Only option, keeps the script a bit more simple, and allows you to call the VM Role Deployments in the same way the Tenant Portal calls them. This means you can leverage the MicrosoftCompute.VMRole SPF event for monitoring and notification if desired (again, see the following blog post: Automation–Monitoring and Notifying in Windows Azure Pack with SMA for more information).

Let’s get started…

As you know, using SMA means using PowerShell workflow. It is a bit more complex than just “regular PowerShell”, but many liberties can be taken when leveraging the InlineScript functionality. Taking liberties, making my life easier, and built-in PowerShell Remoting are the three main reasons I chose to go with InlineScript in these examples. I am not opposed to other methods, this is just the one I chose.

So, the overall structure of my SMA Runbooks is as follows:

  1. Subscription-Create-Dispatcher: Top Tier Dispatch Runbook; hooked directly to the SPF event call; captures SPF event Data (Subscription and User info, etc.); used to decide which Subscription to act against, and which User to act for; calls numerous other Sub-Runbooks (including the one(s) for Tenant Virtual Network Creation and VM Role Deployments – this is where multiple VM Role Deployments can be called at once)
  2. Deploy-TenantVMRole: Middle Tier Sub-Runbook; called by Dispatch Runbook; collects, parses and organizes deployment data from input parameter and variable data; calls Lowest Tier Sub-Runbook with specific deployment criteria
  3. Deploy-VMRole: Lowest Tier Sub-Runbook; called by Middle Tier Sub-Runbook; collects and uses input parameter data; executes all required VM Role Deployment calls (this is where some of the “options” come in – for example, instead of executing only against the WAP Tenant API, the actual VM Role Deployment could execute the required VMM Cmdlet command (Add-CloudResource))

And to help with the overall visualization as well as where these SMA Runbooks fit in the all-up process, here is another image:

image


Did someone say Scripts?

Yeah, yeah. I am getting there. Remember, it is not only about the “code”, but also how that “code” came to be. Or at least that is my reasoning for taking you on this journey.

Here goes…

I am going to start with the Lowest Tier Sub-Runbook (Deploy-VMRole), and work up.


Deploy a Gallery Item VM Role

The following PowerShell workflow script (Deploy-VMRole) will deploy a WAP Gallery Item VM Role with the following settings (with the WAP Tenant API Only):

  • WAP Server: <Name of WAP Admin Server defined with parameter used by InlineScript Remoting and by WAP Admin PowerShell Cmdlet>
  • Credentials: <PSCredential defined with parameter used by InlineScript Remoting>
  • Tenant Portal Address: <FQDN of the WAP Tenant Portal Server defined with parameter used to generate URIs for Invoke-WebRequest API Calls to WAP Tenant API>
  • Subscription ID: <Subscription ID for the User for whom the VM Roles will be deployed, defined with parameter>
  • User ID: <User ID of the User for whom the VM Roles will be deployed, defined with parameter>
  • Gallery Item Name: <Partial or Full Name of the Gallery Item VM Role to be Deployed, defined with parameter, used to match the Gallery Item Reference Data to generate the Gallery Item ResDef URI>
  • Gallery Item Version: <Version of the Gallery Item VM Role to be Deployed, defined with parameter, used to identify correct version of the Gallery Item VM Role>
  • ResDefConfig JSON: <Resource Definition Configuration data (in JSON format), defined with parameter, used to satisfy the ResDef, part of the Gallery Item VM Role Payload>
  • Cloud Service Name: <Name of the Cloud Service, defined with parameter, used in the Gallery Item VM Role Deployment>
  • VM Role Name: <Name of the VM Role to be Deployed, defined with parameter, part of the Gallery Item VM Role Payload>

Note     Each of these parameters are leveraged throughout the Deploy-VMRole workflow and are configured for maximum re-use and flexibility. The intention of this Low Tier Sub-Runbook is to remain as generic as possible. Specific parameter setting occurs at the Top and Middle Tier Runbooks.

Example PowerShell workflow script for Deploy-VMRole

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
workflow Deploy-VMRole
{
    param
    (
        [string]$WAPServer,
        [PSCredential]$Creds,
        [string]$TenantPortalAddress,
        [string]$SubscriptionID,
        [string]$UserID,
        [string]$GalleryItemName,
        [string]$GIVersion,
        [string]$ResDefConfigJSON,
        [string]$CloudServiceName,
        [string]$VMRoleName
    )

    $VMRole = InlineScript {

        $AdminURI = "https://" + $Using:WAPServer + ":30004"
        $AuthSite = "https://" + $Using:WAPServer + ":30072"
        $ClientRealm = "http://azureservices/AdminSite"
        $token = Get-MgmtSvcToken -Type Windows -AuthenticationSite $AuthSite -ClientRealm $ClientRealm -DisableCertificateValidation

        $Headers = @{
            Authorization = "Bearer $token"
            "x-ms-principal-id" = $Using:UserID }

        # Get Gallery Item Reference
        $GIReferenceUri = "https://{0}:30005/{1}/Gallery/GalleryItems/$/MicrosoftCompute.VMRoleGalleryItem?api-version=2013-03" -f $Using:TenantPortalAddress,$Using:SubscriptionID
        $GIReferenceData = [xml](Invoke-WebRequest -Uri $GIReferenceUri -Headers $Headers -UseBasicParsing | Select-Object -ExpandProperty Content)
        $GalleryItemREF = $GIReferenceData.feed.entry.content.properties.resourcedefinitionUrl | ? {$_ -match $Using:GalleryItemName}

        # Get Gallery Item Resource Definition
        $GIResDEFUri = "https://{0}:30005/{1}/{2}/?api-version=2013-03" -f $Using:TenantPortalAddress,$Using:SubscriptionID,$GalleryItemREF
        $GIResourceDEFJSON = Invoke-WebRequest -Uri $GIResDEFUri -Headers $Headers -UseBasicParsing | Select-Object -ExpandProperty Content 
     
        #Convert ResDef JSON to Dictionary
        [System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions") | Out-Null
        $JSSerializer = New-Object System.Web.Script.Serialization.JavaScriptSerializer
        $ResDef = $JSSerializer.DeserializeObject($GIResourceDEFJSON)

        #Add ResDefConfig JSON to Dictionary
        $ResDefConfig = New-Object 'System.Collections.Generic.Dictionary[String,Object]'
        $ResDefConfig.Add("Version",$Using:GIVersion)
        $ResDefConfig.Add("ParameterValues",$Using:ResDefConfigJSON)

        # Set Gallery Item Payload Variables
        $GISubstate = $null
        $GILabel = $Using:VMRoleName
        $GIName = $Using:VMRoleName
        $GIProvisioningState = $null
        $GIInstanceView = $null

        # Set Gallery Item Payload Info
        $GIPayload = @{
            "InstanceView" = $GIInstanceView
            "Substate" = $GISubstate
            "Name" = $GIName
            "Label" = $GILabel
            "ProvisioningState" = $GIProvisioningState
            "ResourceConfiguration" = $ResDefConfig
            "ResourceDefinition" = $ResDef
            }

        # Convert Gallery Item Payload Info To JSON
        $GIPayloadJSON = ConvertTo-Json $GIPayload -Depth 7

        # Get Cloud Services
        $CloudServicesUri = "https://{0}:30005/{1}/CloudServices?api-version=2013-03" -f $Using:TenantPortalAddress,$Using:SubscriptionID
        $CloudServicesData = [xml](Invoke-WebRequest -Uri $CloudServicesUri -Headers $Headers -UseBasicParsing | Select-Object -ExpandProperty Content)
        $CloudService = $CloudServicesData.feed.entry.content.properties.Name | ? {$_ -match $Using:CloudServiceName}
        if (!$CloudService) {
            # Set Cloud Service Configuration
            $CloudServiceConfig = @{
                "Name" = $Using:CloudServiceName
                "Label" = $Using:CloudServiceName
                }

            # Convert Cloud Service Configuration To JSON
            $CloudServiceConfigJSON = ConvertTo-Json $CloudServiceConfig

            $CloudServicesData = [xml](Invoke-WebRequest -Uri $CloudServicesUri -Headers $Headers -Method Post -Body $CloudServiceConfigJSON -ContentType "application/json" -UseBasicParsing)
            $CloudService = $CloudServicesData.entry.content.properties.Name | ? {$_ -match $Using:CloudServiceName}
        }

        # Set Gallery Item VM Role Deploy URI
        $GIDeployUri = "https://{0}:30005/{1}/CloudServices/{2}/Resources/MicrosoftCompute/VMRoles/?api-version=2013-03" -f $Using:TenantPortalAddress,$Using:SubscriptionID,$CloudService

        # Deploy Gallery Item VM Role
        $VMRoleDeployed = Invoke-WebRequest -Uri $GIDeployUri -Headers $Headers -Method Post -Body $GIPayloadJSON -ContentType "application/json" -UseBasicParsing

        Return $VMRoleDeployed

    } -PSComputerName $WAPServer -PSCredential 
$Creds

$VMRole


}

Note     There are a ton of nuances within this example workflow. It is because of this, I outlined The Process above. In fact, this example workflow includes the following process steps: 1-5, 8-13 and all Cloud Service Creation sub-steps. Steps 6 and 7 occur in the Middle Tier Sub-Runbook.

Calling the Deploy-VMRole PowerShell workflow

The following PowerShell workflow script (Deploy-TenantVMRole) will call the Deploy-VMRole workflow with the following settings:

  • WAP Server: <Name of WAP Admin Server, defined by SMA Variable>
  • Credentials: <PSCredential, defined by SMA Variable>
  • Tenant Portal Address: <FQDN of the WAP Tenant Portal Server, defined by SMA Variable>
  • Subscription ID: <Subscription ID for the User for whom the VM Roles will be deployed, defined by parsing the $OwnerUserRole parameter>
  • User ID: <User ID of the User for whom the VM Roles will be deployed, defined by parsing the $OwnerUserRole parameter>
  • Gallery Item Name: <Partial or Full Name of the Gallery Item VM Role to be Deployed, defined by parsing the $GalleryItemToDeploy parameter>
  • Gallery Item Version: <Version of the Gallery Item VM Role to be Deployed, defined by parsing the $GalleryItemToDeploy parameter>
  • Cloud Service Name: <Name of the Cloud Service, generated by combining hardcoded custom variable data and $SubscriptionID variable>
  • OS Disk: <Name of the Default OS Disk to be used in the Gallery Item VM Role Deployment, defined by SMA Variable>
  • Password: <Password used in the Gallery Item VM Role Deployment, defined by SMA Variable>
  • ResDefConfig JSON: <Resource Definition Configuration data (in JSON format), defined by numerous parameters/variables, contains common and Gallery Item VM Role specific data>

Note     You may keep these example settings, or modify to fit your deployment specifications. Each of these parameters/variables are leveraged in the Deploy-VMRole workflow call and are configured for maximum re-use and flexibility. The intention of this Middle Tier Sub-Runbook is to balance common and Gallery Item VM Role specific data so that the line between them is clear and updates are simple (as the number of Gallery Item VM Roles in your environment grows). Even more specific parameter settings occur in the Top Tier Runbook.

Example PowerShell workflow script for Deploy-TenantVMRole

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
workflow Deploy-TenantVMRole
{
    param
    (
        [string]$OwnerUserRole,
        [string]$GalleryItemToDeploy,
        [string]$VMRoleName,
        [string]$VMRoleNamePattern,
        [string]$VMRoleSize
    )
   
    #Define Variables
    $WAPServer = Get-AutomationVariable -Name 'WAP Admin Server'
    $Creds = Get-AutomationPSCredential -Name 'PSCredential Name'
    $TenantPortalAddress = Get-AutomationVariable -Name 'WAP Tenant Server FQDN'
    $SubscriptionID = $OwnerUserRole.Split("_")[1]
    $UserID = $OwnerUserRole.Split("_")[0]
    $GalleryItemName = $GalleryItemToDeploy.Split(";")[0]
    $GIVersion = $GalleryItemToDeploy.Split(";")[1]
    $CloudServiceName = "CloudService-4-{0}" -f $SubscriptionID
    $OSDisk = Get-AutomationVariable -Name 'Default VM Role OS Disk'
    $Password = Get-AutomationVariable -Name 'Password'
   
    # Create Gallery Item Parameter Hashtable (for Common Data)
    $GIParamList = @{
        VMRoleVMSize = $VMRoleSize
        VMRoleOSVirtualHardDiskImage = $OSDisk
        VMRoleAdminCredential = "administrator:{0}" -f $Password
        VMRoleTimeZone = "Pacific Standard Time"
        VMRoleComputerNamePattern = $VMRoleNamePattern
        VMRoleNetworkRef = "Tenant Network ({0})" -f $UserID
        }

    # Add to Gallery Item Parameter Hashtable (for GI Specific Data)
    if ($GalleryItemName -eq "DomainController")
    {
        $GIParamList += @{DomainControllerWindows2012DomainDNSName = $UserID.Split("@")[1]} 
        $GIParamList += @{DomainControllerWindows2012DomainNETBIOSName = ($UserID.Split("@")[1]).Split(".")[0]}
        $GIParamList += @{DomainControllerWindows2012SafeModeAdminPassword = $Password}
    }
   
    # Convert Gallery Item Parameter Hash To JSON
    $ResDefConfigJSON = ConvertTo-Json $GIParamList
   
    Deploy-VMRole -WAPServer $WAPServer -creds $Creds -TenantPortalAddress $TenantPortalAddress `
        -SubscriptionID $SubscriptionID -UserID $UserID -GalleryItemName $GalleryItemName `
        -GIVersion $GIVersion -ResDefConfigJSON $ResDefConfigJSON -CloudServiceName $CloudServiceName `
        -VMRoleName $VMRoleName
}

Did you miss it?

Up to this point, you have been inundated with “generic” workflow examples to Deploy any Gallery Item VM Role. So, you may have actually missed where this post went from “generic” to “specific”. If you did, look up at the  Deploy-TenantVMRole workflow script again, and check out lines 34-40ish. For this Middle Tier Sub-Runbook, that is the extent of the “specific”. Believe me, I would have loved to make this and the Lowest Tier Sub-Runbook 100% Generic, but it is just not possible. More about this, and the decisions I made are in the note below.

Note     The Gallery Item VM Role specific data (as it relates to the other Gallery Item VM Roles in this blog series, and from the Building Clouds Blog Gallery) is kept in a separate section (lines 34-40ish above) and is surrounded by “if” logic. The intention of this section is to logically store the Gallery Item VM Role specific data by $GalleryItemName. I put quite a bit of thought on where the best place for this “unique” data should live within these example workflows, as well as more dynamic ways to process it – this happens to be where it landed and what it looks like. And obviously, this will not work for every possible VM Role created. But it is one idea/implementation that has worked for my team’s deployment scenario. Oh, and before I forget, this is the portion of the examples where Steps 6 and 7 of The Process take place.

Calling the Deploy-TenantVMRole PowerShell workflow

The following PowerShell workflow script (Subscription-Create-Dispatcher) will call the Deploy-TenantVMRole workflow with the following settings:

  • Gallery Item To Deploy: <Concatenated string made up of the Gallery Item Name (full or partial) and Gallery Item Version in GalleryItemName:1.0.0.0 format, defined by custom data>
  • Owner User Role: <Concatenated string made up of of the User ID (email address) and Subscription ID (GUID) in email@address.com_SUBSC_RIPTION_GUID_STRING, defined by SPF event Data stored in the $resourceObject input variable>
  • VM Role Name: <Name of the VM Role to be Deployed (will be how the VM Role is seen in the Tenant Portal), defined by custom data>
  • VM Role Name Pattern: <Name Pattern of the VM to be Deployed (will be how the VM is named within the hypervisor) in NN## format, defined by custom data>
  • VM Role Size: <Size of the VM Role to be Deployed, restricted to available VMM Hardware Profile/Gallery Item VM Role definitions, defined by custom data>

Note     The data entered here is completely customizable and should fit your deployment specifications. The intention of this Top Tier Runbook is to set Gallery Item VM Role specific variables and parameters to be passed on to the much more generic Middle and Lowest Tier Sub-Runbooks. In fact, this Top Tier Runbook is where the Tenant configuration is built out:

  • Virtual Network Creation
  • Multiple concurrent (and/or dependent) Gallery Item VM Role Deployments
  • Job Monitoring
  • Notifications
  • etc.

Remember, as the Top Tier Runbook, it is the one called by the Subscription.Create WAP/SPF event, configured in the VM Clouds Resource Provider:

image

It is the main “hook” for the Tenant Provisioning Process of “Subscribe to a Plan, Get a Fully Deployed Set of Collaborative Workloads”.

Example PowerShell workflow script for Subscribe-Create-Dispatcher

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
workflow Subscription-Create-Dispatcher
{
    param
    (
        [object]$resourceObject
    )

    if ($resourceObject.AdminID.Length -gt 27) { $AdminId = $AdminId.SubString(0,27) } else { $AdminId = $resourceObject.AdminId }
    $OwnerUserRole = $AdminId + "_" + $resourceObject.SubscriptionID
    $SubscriptionName = $resourceObject.SubscriptionName
   
    $VMMServer = Get-AutomationVariable -Name 'VMM Server'
    $LogicalNetworkName = Get-AutomationVariable -Name 'Default VMM Logical Network'
    $PSEmailServer = Get-AutomationVariable -Name 'SMTP Server'
    $PSEmailFrom = Get-AutomationVariable -Name 'SMTP From Email'
    $PSEmailCC = Get-AutomationVariable -Name 'PSEmailCC'
   
    if ($SubscriptionName -eq "Collaboration Workloads")
    {
        $CloudName = "Tenant Cloud"
       
        Create-VMNetwork -VmmServerName $VMMServer -OwnerUserRole $OwnerUserRole -CloudName $CloudName -LogicalNetworkName $LogicalNetworkName
       
        Send-SMTPNotification -SendNotificationType "Plans" -PSEmailFrom $PSEmailFrom -PSEmailTo $AdminId -PSEmailServer $PSEmailServer -PSEmailCC $PSEmailCC -WorkloadName $SubscriptionName
        $SubscriptionName + " Plan Selected"
       
        "Deploying Active Directory"
        Deploy-TenantVMRole -GalleryItemToDeploy "DomainController;1.0.0.0" `
            -OwnerUserRole $OwnerUserRole -VMRoleName "ActiveDirectory" `
            -VMRoleNamePattern "DC##" -VMRoleSize "ExtraSmall"
    }
}

Note     This example is exactly what we have been using on my team in our Demo/Test/Dev environment. As you can see, it includes more than just a call to the Deploy-TenantVMRole workflow. In fact, it also includes:

  • Extraction of SPF event data from the $resourceObject input variable
  • Example generation of the $OwnerUserRole string
  • Usage of SMA Variables
  • Logic for Subscription dispatching based on Subscription Name (one example provided)
  • Invocation of the Create-VMNetwork workflow (to create the Tenant Virtual Network described above)
  • Invocation of the Send-SMTPNotification workflow (to send email notifications to the user about post-subscription activities) - For more information (and a specific example) about how the Send-SMTPNotification workflow, see the following blog post: Automation–Monitoring and Notifying in Windows Azure Pack with SMA
  • And finally, Invocation of the Deploy-TenantVMRole workflow (with Gallery Item VM Role specific data)

By the way - I realize that some of the direct text output and use of hardcoded values could be better served in Write-Verbose commands and usage of variables (respectively), but for our environment, and for this example, this is what I went with. That said, you may keep these example settings, or modify to fit your deployment specifications.

Oh, and if you are wondering about Job Monitoring, our environment has that too – it just so happens to be hooked to a different Top Level Dispatch Runbook: VMRole-Create-Dispatcher. And because the Deploy-VMRole workflow leverages the same exact method for VM Role creation as the Tenant Portal, the WAP/SPF event for MicrosoftCompute.VMRole works the same:

image

So, for fun…

Example PowerShell workflow script for VMRole-Create-Dispatcher

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
workflow VMRole-Create-Dispatcher
{
    param
    (
        [object]$resourceObject
    )
   
    $VMRoleOwner = $resourceObject.Owner
    $VMRoleName = $resourceObject.Name
    $SCJobID = $resourceObject.MostRecentTask.ID
    $PSEmailServer = Get-AutomationVariable -Name 'SMTP Server'
    $PSEmailFrom = Get-AutomationVariable -Name 'SMTP From Email'
    $PSEmailCC = Get-AutomationVariable -Name 'PSEmailCC'
   
    Send-SMTPNotification -SendNotificationType "Workloads" -PSEmailFrom $PSEmailFrom -PSEmailTo $VMRoleOwner -PSEmailServer $PSEmailServer -PSEmailCC $PSEmailCC -WorkloadName $VMRoleName 
    "Job ID ($SCJobID) is being monitored"
    $VMRoleDeploy = Monitor-VMMJobStatus -SleepDuration 300 -SCJobID $SCJobID -OutputStatus $false
    "Deploy of $VMRoleName : $VMRoleDeploy"
    Send-SMTPNotification -JobState $VMRoleDeploy -SendNotificationType "Workloads-Complete" -PSEmailFrom $PSEmailFrom -PSEmailTo $VMRoleOwner -PSEmailServer $PSEmailServer -PSEmailCC $PSEmailCC -WorkloadName $VMRoleName
     
}

The combination of Job Monitoring and Notifications allow for the creation of emails like this:

image  image


BONUS!

Oh, alright…Here is another example script.

What does it do?

It is the portion of the Deploy-VMRole required if you wanted to leverage the VMM Cmdlet command Add-CloudResource instead of going WAP Tenant API Only

Note     If you want to go this route, this example script replaces the existing script portion within the Deploy-VMRole starting around line 45, right through to the end (before the workflow’s end bracket).

Sound interesting?

Alternate Example PowerShell workflow script for a portion of the Deploy-VMRole

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
$GIVMRole = InlineScript {
       
    #Convert ResDef JSON to Dictionary
    [System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions") | Out-Null
    $JSSerializer = New-Object System.Web.Script.Serialization.JavaScriptSerializer
    $ResDef = $JSSerializer.DeserializeObject($Using:ResDefJSON)

    #Add ResDefConfig JSON to Dictionary
    $ResDefConfig = New-Object 'System.Collections.Generic.Dictionary[String,Object]'
    $ResDefConfig.Add("Version",$Using:GIVersion)
    $ResDefConfig.Add("ParameterValues",$Using:ResDefConfigJSON)

    Get-SCVMMServer -ComputerName $Using:VMMServer -ForOnBehalfOf | Out-Null
    $OwnerUserRole = Get-SCUserRole | ? {$_.Name -match $Using:UserID}

    #Create a CloudService
    $Cloud = Get-SCCloud -Name $Using:CloudName -OnBehalfOfUser $Using:UserID -OnBehalfofUserRole $OwnerUserRole
    $CloudSevice = Get-CloudService -Name $Using:CloudServiceName  -OnBehalfOfUser $Using:UserID -OnBehalfofUserRole $OwnerUserRole
    if (!$CloudSevice) {
        $CloudSevice = New-CloudService -Cloud $Cloud -Name $Using:CloudServiceName -OnBehalfOfUser $Using:UserID -OnBehalfofUserRole $OwnerUserRole
    }

    $CloudResource = Add-CloudResource -CloudService $CloudSevice -ResourceDefinition $ResDef -ResourceName $Using:VMRoleName `
        -OnBehalfOfUser $Using:UserID -OnBehalfofUserRole $OwnerUserRole -ResourceConfiguration $ResDefConfig -RunREST

    Return $CloudResource

} -PSComputerName $VMMServer -PSCredential 
$Creds

$GIVMRole

Note     Again, this is still example workflow script to be executed as the Service Admin, this time against VMM directly, instead of leveraging the WAP Tenant API. You will see that I leveraged a second InlineScript, this time to connect to the VMM Server (as the WAP Tenant API calls that come before it execute via InlineScript connected to the WAP Server). You will also see that we are still leveraging “OnBehalfOf” functionality for the VMM commands, this is required if you want proper Tenant ownership of the deployed resources.

The biggest thing to know about the Add-CloudResource command is that the -ResourceDefinition and –ResourceConfiguration  parameters require Dictionary Objects. This is why I have both the ResDef and ResDefConfig convert/deserialization steps in this section. In fact, this portion of the script includes steps 8-13 and all Cloud Service Creation sub-steps from The Process described above.

Finally, it is worth restating - JSON is the preferred method for data storage and transfer between sections of the script (from InlineScript to InlineScript and workflow to workflow).


What’s Missing?

Well, from the Service Admin perspective, nothing really. Sure, I did not provide a non-workflow PS script example, but that is easy enough to generate based on the above examples.

From the Tenant Admin perspective, that is another story. In fact, I think it is time for another visual aid:

image

Why?

Well, it is not for a lack of trying. And actually, the scripts are 99% complete. So are my thoughts on how to deliver the information…It came down to a question of prioritization - for both this blog post series, as well as product demos/videos on my plate. In the end, the Tenant Provisioning Process of “Subscribe to a Plan, Get a Fully Deployed Set of Collaborative Workloads” took precedence.

Besides, isn’t this blog post long enough already?

When?

The very next blog post in this series! And thus, I have updated the Table of Contents below…


But wait, WHY would I want to do this?

Great question! Here are some use cases (all from the Service Admin persona):

  • Use Case 1: Provision a nearly complete environment to your Tenants/End Users at the click of a self-service button (Subscription to a Plan)
  • Use Case 2: Enable Gallery Item VM Role deployment from a custom self-service portal, feeding the SMA Runbooks with more dynamic parameter data
  • Use Case 3: Integrate Gallery Item VM Role deployment with an existing customer facing interface
  • Use Case 4: Develop and Execute SMA Runbooks (potential to take in Parameters during execution) to deliver Gallery Item VM Roles on-demand

Note     These are just some of the use cases I could come up with off the top of my head. I am sure you have many more scenarios in mind.


Blog Series Table of Contents

  1. Part 1: Intro & TOC
  2. Part 2: Automated Deployment of Tenant Network and Identity Workload (Isolated Tenant Virtual Network & Active Directory VM Role; from the Service Admin Persona)
  3. Part 3: Automated Deployment of the Identity Workload as a Tenant Admin (Active Directory VM Role; from the Tenant Admin Persona)
  4. Part 4: Automated Deployment of Tenant Workloads (Lync, SharePoint, and Exchange) (Lync, SharePoint, and Exchange VM Roles; from both Service Admin and Tenant Admin Personas)
  5. Part 5: Working with the SQL Server resource provider, and the ITIL dilemma (by Bruno Saille)
  6. Part 6: TBD (We hope to have something around: Value Added Services/Offerings and Ongoing Automated Maintenance/Operations)

TechNet Gallery Contribution and Download

The download (Windows Azure Pack Tenant Provisioning Automation Toolkit.zip) includes (14) files.

For more information please refer to Part 4 of this blog series (near the bottom of the post), or in the description of the TechNet Gallery Contribution itself.

Download the Windows Azure Pack Tenant Provisioning Automation Toolkit from TechNet Gallery here:

BC-DLButtonDark


Thanks for checking out my latest blog series! For more information, tips/tricks, and example solutions for Automation within System Center, Windows Azure Pack, Windows Azure, etc., be sure to check out the other blog posts from Building Clouds in the Automation Track!

enJOY!