Yay! Part 4 has finally arrived…

And by Part 4, I am obviously talking about the next big post in “The New World of Tenant Provisioning with Windows Azure Pack” blog series.


What is this one all about?

Automated Deployment of Tenant Workloads (Lync, SharePoint, and Exchange)

What does that mean?

Well, let’s first take a look back: Part 1 (here) was the Intro/TOC for this fine blog series; Part 2 (here) was all about Automated Deployment of Tenant Network and Identity Workload (Active Directory Gallery Item VM Role) from the Service Admin Persona; and Part 3 (here) was all about Automated Deployment of the Identity Workload (Active Directory Gallery Item VM Role) from the Tenant Admin Persona.

So what that means is, in Part 4, we only have one major aspect of Gallery Item VM Role Deployment left to discuss in the series: Tenant Workloads (Lync, SharePoint, and Exchange Gallery Item VM Roles)


Up to this point in the blog series, we have covered an example for each of the following deployment options:

image

Note     In this diagram, we also see that deployment options for both administrator personas have been covered. And we did this with a specific example in mind: the Active Directory Gallery Item VM Role. This was due to the fact that the subsequent example Gallery Item VM Role deployments in this blog series take a strong dependency on Active Directory. In this way, both the Gallery Item VM Role for Active Directory and the Tenant Network are “table stakes”.


Truth be told, based on just Parts 2 and 3 of this blog series, you really have everything necessary to deploy not only the Gallery Item VM Role for Active Directory, but any Gallery Item VM Role you have built (or pulled down off of WebPI (or our Blog)).

Which leads to the logical next step…

Describing what it takes and providing the necessary script updates (to what you have seen so far) to deploy the Gallery Item VM Roles for Lync, SharePoint, and Exchange (for both personas).


Which means, at least in part, that these three diagrams happen to also apply to this blog post:

imageimage


Automated Deployment of Tenant Workloads

(Lync, SharePoint, and Exchange VM Roles; from both Service Admin and Tenant Admin Personas)

Because we established a solid foundation in Parts 2 and 3 of the series, this post really just highlights the script updates necessary to get Automated WAP Deployments to work for OUR PUBLISHED Lync, SharePoint, and Exchange Gallery Item VM Roles. The reason I say “OUR PUBLISHED” in yelling-case is to emphasize which Gallery Item VM Roles the example scripts in this post actually directly relate. If you are wondering, “OUR PUBLISHED” Lync, SharePoint, and Exchange Gallery Item VM Roles can be found here: Windows Azure Pack VM Role Gallery Items for Collaboration Workloads

Wait! Wait! Wait! What’s the difference in the script from VM Role to VM Role?

Good question, glad you asked.

Well, as you saw in Part 2 and 3, no one generic script can be created that handles the deployment from beginning to end. This is due to the fact that each VM Role Definition can vary, based on how that VM Role was created, what fields were included, and which Resource Extensions were added. And while I did my best to keep my scripts generic, there were just portions that had to be hardcoded - dun! dun! dun! The good news is, the hardcoded – dun! dun! dun! stuff is kept to where the scripts differ. Meaning, logic can be introduced to dynamically add the appropriate hardcoded - dun! dun! dun! script portions based on VM Role type.

Will I be able to apply what I learn here against the VM Roles I create?

Yes. There will be a “discovery” section in the post which takes you through how to enumerate “What’s different” or “What’s required” as it relates to the Resource Definition (ResDef/ResDefExt), and Resource Definition Configuration (ResDefConfig).

What about the Tenant Virtual Network?

This topic will not be discussed again here in this post. Please refer to Part 2 of this blog series for more information and example script.

Note     The TechNet Gallery Contribution download will also include the example script(s) for automatically creating a Tenant Virtual Network.


The Scope

Just like in Parts 2 and 3, there are various options to choose from when deploying Gallery Item VM Roles. These options are depicted above, in the first image (the one with all the green checkmarks). To keep this blog post manageable, I will be providing the example Runbooks / Scripts / Guidance in the following order:

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

  • Existing: SMA Runbook that is the same for each Gallery Item VM Role Deployment
  • New: Updated SMA Runbooks for the Lync Gallery Item VM Role Deployment
  • New: Updated SMA Runbooks for the SharePoint Gallery Item VM Role Deployment
  • New: Updated SMA Runbooks for the Exchange Gallery Item VM Role Deployment
  • Future Discovery: How to enumerate ResDef/ResDefExt and ResDefConfig Requirements for any Gallery Item VM Role

Tenant Administrator from a PowerShell Script (With the Public WAP Tenant API)

Instead of making a whole big section, reiterating the same thing over and over, I will simply be providing the following:

  • New: Example Gallery Item VM Role Deployment Script for Active Directory, Lync, Exchange, or SharePoint (as a Tenant Admin against the Public WAP API)
  • Future Discovery: Modified Example PowerShell script to enumerate ResDef/ResDefExt and ResDefConfig Requirements for any Gallery Item VM Role (as a Tenant Admin against the Public WAP API)

Out of Scope

There are a couple topics out of scope for this particular blog post, as they have been covered previously within the series.

  1. Automated Tenant Virtual Network Creation (can be found in Part 2)
  2. Automated Active Directory Gallery Item VM Role Deployment (can be found in both Part 2 and Part 3)
  3. Detailed Review of “The Process” for constructing a Gallery Item VM Role Deployment Script (can be found in both Part 2 and Part 3)
  4. Example VMM PowerShell for Gallery Item VM Role Deployment (“Add-CloudResource” can be found in Part 2)
  5. Pre-Requisites for WAP Tenant API Authentication (“bearer token authorization” can be found in Part 2)
  6. Pre-Requisites for Public WAP Tenant API Authentication (“Windows Azure PowerShell Module” and “certificates” can be found in Part 3)

In other words, from a deployment perspective (and from the Tenant’s view), this is our starting point:

image


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

Existing: SMA Runbook that is the same for each Gallery Item VM Role Deployment

If we take a look at the “overall structure of my SMA Runbooks” section from Part 2, the following SMA Runbook will be the same for each Gallery Item VM Role Deployment (well, depending on your implementation, but definitely for this example) and can be leveraged generically:

image

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


}

New: Updated SMA Runbooks…

If we take another look at the “overall structure of my SMA Runbooks” section from Part 2, these two SMA Runbooks will have subtle differences for each Gallery Item VM Role Deployment:

image

Note     I provide the updated SMA Runbook examples for each Tenant Workload Deployment below.


Before we dive into the SMA Runbook updates, I want to level-set…

Because more significant updates take place in the Deploy-TenantVMRole SMA Runbook, this section of the post will concentrate more on it, rather than the Subscription-Create-Dispatcher SMA Runbook.

Remember in both Parts 2 and 3 there was a portion of the example PowerShell that created the Gallery Item Parameter Hashtable (for Common Data) and then added to the Gallery Item Parameter Hashtable (for GI Specific Data)?

If not, here is an image to help jog your memory:

image

It is right below this section in the Deploy-TenantVMRole SMA Runbook where we will be adding the updates. Essentially, more logic will be introduced to dynamically add the appropriate hardcoded - dun! dun! dun! script portions based on VM Role type.

Note     I will be including the script portions in each respective Tenant Workload sub-section below. Then an all-up Deploy-TenantVMRole SMA Runbook with configuration for all 4 (including Active Directory) Tenant Workload Deployments will be made available after the last sub-section. Also included, will be the all-up Subscription-Create-Dispatcher SMA Runbook with calls for all 4 (including Active Directory) Tenant Workload Deployments.


…for the Lync Gallery Item VM Role Deployment

001
002
003
004
005
if ($GalleryItemName -eq "Lync")
{
    $GIParamList += @{RunAsDomainToJoin = $UserID.Split("@")[1]} 
    $GIParamList += @{LyncServer2013RunAsCredential = "{0}\administrator:{1}" -f ($UserID.Split("@")[1]).Split(".")[0],$Password}
}

…for the SharePoint Gallery Item VM Role Deployment

001
002
003
004
005
006
if ($GalleryItemName -eq "SharePoint")
{
    $GIParamList += @{RunAsDomainToJoin = $UserID.Split("@")[1]}
    $GIParamList += @{SharePointServer2013RunAsCredential = "{0}\administrator:{1}" -f ($UserID.Split("@")[1]).Split(".")[0],$Password}
    $GIParamList += @{SharePointServer2013InstallChoice = "SingleServer"}
}

…for the Exchange Gallery Item VM Role Deployment

001
002
003
004
005
006
if ($GalleryItemName -eq "Exchange")
{
    $GIParamList += @{DomainJoinDomainToJoin = $UserID.Split("@")[1]}
    $GIParamList += @{ExchangeServer2013CU2RunAsCredential = "{0}\administrator:{1}" -f ($UserID.Split("@")[1]).Split(".")[0],$Password}
    $GIParamList += @{ExchangeServer2013CU2ExchangeServer2013CU2Organization = "ExchangeOrg"}
}

Note     For each of these example script portions, I kept most of the Hashtable Variable building “complex” and/or hardcoded – dun! dun! dun! on purpose. I wanted to avoid creating and leveraging extra variables outside these individual IF blocks. In these examples, much of the string formatting is the same from IF block to IF block, so you can create a “Credential” string before all this, and leverage it throughout.

Disclaimer     This is a set of examples to handle the GI Specific Data. There are other (and likely better) ways to do this same thing. If you have improvements, please leverage them. If you would like to share those improvements with the community, please leave comments, or send a link to your related blog post. I am always happy to cross-post.


Example PowerShell workflow script for Deploy-TenantVMRole

(with IF blocks for DomainController, Lync, SharePoint, and Exchange)

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
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}
    }
   
    if ($GalleryItemName -eq "Lync")
    {
        $GIParamList += @{RunAsDomainToJoin = $UserID.Split("@")[1]} 
        $GIParamList += @{LyncServer2013RunAsCredential = "{0}\administrator:{1}" -f ($UserID.Split("@")[1]).Split(".")[0],$Password}
    }
   
    if ($GalleryItemName -eq "SharePoint")
    {
        $GIParamList += @{RunAsDomainToJoin = $UserID.Split("@")[1]}
        $GIParamList += @{SharePointServer2013RunAsCredential = "{0}\administrator:{1}" -f ($UserID.Split("@")[1]).Split(".")[0],$Password}
        $GIParamList += @{SharePointServer2013InstallChoice = "SingleServer"}
    }
   
    if ($GalleryItemName -eq "Exchange")
    {
        $GIParamList += @{DomainJoinDomainToJoin = $UserID.Split("@")[1]}
        $GIParamList += @{ExchangeServer2013CU2RunAsCredential = "{0}\administrator:{1}" -f ($UserID.Split("@")[1]).Split(".")[0],$Password}
        $GIParamList += @{ExchangeServer2013CU2ExchangeServer2013CU2Organization = "ExchangeOrg"}
    }
   
    # 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
}

Note     Obviously I took some liberties with the data sharing in this example. Before I started this SMA Runbook, I knew that each of our Gallery Item VM Roles shared a set of “Common Data” for the Gallery Item Parameter Hashtable. At that point, I just had to figure out which parameters fell into the “GI Specific Data” for each Gallery Item VM Role to  be included in the SMA Runbook. Each set of “GI Specific Data” then gets its own IF block, appending to the “Common Data” already in the Gallery Item Parameter Hashtable. More information on “Discovery” of “GI Specific Data” can be found in the very next section of this post!


Example PowerShell workflow script for Subscribe-Create-Dispatcher

(with calls for each Tenant Workload)

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
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"
        
         #No Logic, Just Sleep in this example
         Start-Sleep -Seconds 600

        "Deploying Lync"
        Deploy-TenantVMRole -GalleryItemToDeploy "Lync;1.0.0.0" `
            -OwnerUserRole $OwnerUserRole -VMRoleName "Lync" `
            -VMRoleNamePattern "LY##" -VMRoleSize "ExtraLarge"

        "Deploying SharePoint"
        Deploy-TenantVMRole -GalleryItemToDeploy "SharePoint;1.0.0.0" `
            -OwnerUserRole $OwnerUserRole -VMRoleName "SharePoint" `
            -VMRoleNamePattern "SP##" -VMRoleSize "ExtraLarge"

        "Deploying Exchange"
        Deploy-TenantVMRole -GalleryItemToDeploy "Exchange;1.0.0.0" `
            -OwnerUserRole $OwnerUserRole -VMRoleName "Exchange" `
            -VMRoleNamePattern "EX##" -VMRoleSize "ExtraLarge"
    }
}

Note     I did not add any “wait logic” in this example. It simply deploys Active Directory, waits 10 minutes and then deploys the other workloads (Lync, SharePoint, and Exchange). For more information on Monitoring and Notifications, refer to this blog post: Automation–Monitoring and Notifying in Windows Azure Pack with SMA.


Future Discovery: How to enumerate ResDef/ResDefExt and ResDefConfig Requirements for any Gallery Item VM Role

So, how did I know which items in the Gallery Item Parameter Hashtable were “Common” and which were “GI Specific”?

Well, other than tearing apart the JSON during my learning process for all this, there is a pretty simple method to extract the necessary data for review.

The following is an example PowerShell script which leverages the WAP Tenant API (non-Public), pulls down the specified Gallery Item VM Roles desired for comparison, and outputs a Hashtable containing the Gallery Item VM Role Name and its required Resource Parameters.

Example PowerShell script for Get-GIResourceParams

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
function Get-GIResourceParams {

    param
    (
        [object]$Headers,
        [string]$TenantPortalAddress,
        [string]$SubscriptionID,
        [string]$GalleryItemName
    )

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

    # Get Gallery Item Resource Definition
    $GIResDEFUri = "https://{0}:30005/{1}/{2}/?api-version=2013-03" -f $TenantPortalAddress,$SubscriptionID,$GalleryItemREF
    $GIResourceDEFJSON = Invoke-WebRequest -Uri $GIResDEFUri -Headers $Headers -UseBasicParsing | Select-Object -ExpandProperty Content 
    $ResDef = ConvertFrom-Json $GIResourceDEFJSON

    Return $ResDef.ResourceParameters.Name

}

$WAPServer = "WAP Admin Server Name"
$UserID = "User ID (email) of User with Subscription"
$TenantPortalAddress = "FQDN of Tenant Portal Address"
$SubscriptionID = "Subscription ID for Specified User"
$GalleryItems = @("DomainController","Lync","SharePoint","Exchange")

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

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

$GIandResourceParams = @{}

foreach ($GalleryItem in $GalleryItems)
{
    $GIResourceParams = Get-GIResourceParams -Headers $Headers -TenantPortalAddress $TenantPortalAddress `
        -SubscriptionID $SubscriptionID -GalleryItemName $GalleryItem

    $GIandResourceParams += @{$GalleryItem = $GIResourceParams}
}

$GIandResourceParams

Note     I leveraged a function within this example script, to minimize duplicate command execution. Modify this example as you see fit. Oh, and it needs to be executed (at least in part) from the WAP Admin Server, or wherever the MgmtSvcAdmin Cmdlets live.

What is the output of this script?

A Hashtable of values you can use to compare/contrast Gallery Item VM Role Resource Parameters:

image

Challenge     I simply provide the Hashtable worth of data, it is up to you to enumerate the data and make something fancy with Compare-Object command to dynamically compare the Resource Parameters on the fly!


Tenant Administrator from a PowerShell Script (With the Public WAP Tenant API)

New: Example Gallery Item VM Role Deployment Script for Active Directory, Lync, Exchange, or SharePoint (as a Tenant Admin against the Public WAP API)

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
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#region GetWAPConnectionData

# Get WAP Subscription Information

$WAPSubscription = Get-WAPackSubscription

# Set Subscription
$SubscriptionID = $WAPSubscription.SubscriptionId

# Get Management Certificate Info
$CertThumb = $WAPSubscription.Certificate.Thumbprint
$CertPath = "Cert:\CurrentUser\My\{0}" -f 
$CertThumb
$Cert
 = Get-Item $CertPath

# Set Tenant Portal Address
$TenantPortalAddress = $WAPSubscription.ServiceEndpoint.Host

# Set Port
$Port = $WAPSubscription.ServiceEndpoint.Port

#endregion GetWAPConnectionData

#region SetVariables

# Set Gallery Item Name and Version for Match and Deploy

$GalleryItemName = "Lync"
$GIVersion = "1.0.0.0"

# Set Common Gallery Item Parameters
$UserID = "tenant@company.com"
$VMRoleNetwork = "Tenant Network ({0})" -f 
$UserID
$CloudServiceName
 = "CloudService-4-{0}" -f 
$SubscriptionID
$VMRoleTZ
 = "Pacific Standard Time"
$OSDisk = "Windows Server 2012 Datacenter"
$OSDiskVersion = "1.0.0.0"
$Password = "Password"

#Set GI Specific Gallery Item Parameters

if ($GalleryItemName -eq "DomainController")
{
    $VMRoleName = "ActiveDirectory"
    $VMRoleNamePattern = "DC##"
    $VMRoleSize = "ExtraSmall"
}

if ($GalleryItemName -eq "Lync")
{
    $VMRoleName = "Lync"
    $VMRoleNamePattern = "LY##"
    $VMRoleSize = "ExtraLarge"
}

if ($GalleryItemName -eq "SharePoint")
{
    $VMRoleName = "SharePoint"
    $VMRoleNamePattern = "SP##"
    $VMRoleSize = "ExtraLarge"
    $SPInstallChoice = "SingleServer"
}

if ($GalleryItemName -eq "Exchange")
{
    $VMRoleName = "Exchange"
    $VMRoleNamePattern = "EX##"
    $VMRoleSize = "ExtraLarge"
    $ExchangeOrg = "ExchangeOrg"
}

#endregion SetVariables

#region GetResDef

# Get Gallery Item Reference

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

# Get Gallery Item Resource Definition
$GIResDEFUri = "https://{0}:{1}/{2}/{3}/?api-version=2013-03" -f $TenantPortalAddress,$Port,$SubscriptionID,
$GalleryItemREF
$GIResourceDEFJSON
 = Invoke-WebRequest -Certificate $Cert -Uri $GIResDEFUri | 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)

#endregion GetResDef

#region SetResDefConfig

# Create Gallery Item Parameter Hashtable (for Common Data)

$GIParamList = @{
    VMRoleVMSize = $VMRoleSize
    VMRoleOSVirtualHardDiskImage = "{0}:{1}" -f $OSDisk,$OSDiskVersion
    VMRoleAdminCredential = "administrator:{0}" -f $Password
    VMRoleTimeZone = $VMRoleTZ
    VMRoleComputerNamePattern = $VMRoleNamePattern
    VMRoleNetworkRef = $VMRoleNetwork
    }

# 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}
}
   
if ($GalleryItemName -eq "Lync")
{
    $GIParamList += @{RunAsDomainToJoin = $UserID.Split("@")[1]} 
    $GIParamList += @{LyncServer2013RunAsCredential = "{0}\administrator:{1}" -f ($UserID.Split("@")[1]).Split(".")[0],$Password}
}
   
if ($GalleryItemName -eq "SharePoint")
{
    $GIParamList += @{RunAsDomainToJoin = $UserID.Split("@")[1]}
    $GIParamList += @{SharePointServer2013RunAsCredential = "{0}\administrator:{1}" -f ($UserID.Split("@")[1]).Split(".")[0],$Password}
    $GIParamList += @{SharePointServer2013InstallChoice = $SPInstallChoice}
}
   
if ($GalleryItemName -eq "Exchange")
{
    $GIParamList += @{DomainJoinDomainToJoin = $UserID.Split("@")[1]}
    $GIParamList += @{ExchangeServer2013CU2RunAsCredential = "{0}\administrator:{1}" -f ($UserID.Split("@")[1]).Split(".")[0],$Password}
    $GIParamList += @{ExchangeServer2013CU2ExchangeServer2013CU2Organization = $ExchangeOrg}
}
   
# Convert Gallery Item Parameter Hashtable To JSON
$ResDefConfigJSON = ConvertTo-Json $GIParamList

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

#endregion SetResDefConfig

#region GenerateGIPayloadJSON

# Set Gallery Item Payload Variables

$GISubstate = 
$null
$GILabel
 = 
$VMRoleName
$GIName
 = 
$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

#endregion GenerateGIPayloadJSON

#region GetOrSetCloudService

# Get Cloud Services

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

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

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

#endregion GetOrSetCloudService

#region DeployGIVMRole

# Set Gallery Item VM Role Deploy URI

$GIDeployUri = "https://{0}:{1}/{2}/CloudServices/{3}/Resources/MicrosoftCompute/VMRoles/?api-version=2013-03" -f $TenantPortalAddress,$Port,$SubscriptionID,$CloudService

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

#endregion DeployGIVMRole

Notes(s)     Once again, I have several notes about the above script. So I will list them here:

  • This is an example script.
  • It has been tested against our Demo/Test/Dev environment multiple times.
  • The following section of the script controls the execution, simply modify these Variables to Deploy the other available VM Roles included within the script:
    image
    The logic is all based on the included IF blocks (two per Gallery Item VM Role).
    image and image
  • It absolutely requires the pre-requisites discussed in Part 3. And while there are alternatives to getting the required variable data based on the Get-WAPackSubscription command, I have found this to be the most efficient/dynamic method.
  • The $TenantPortalAddress Variable may need to  be set to a specific string, rather than being extracted from the information available from the Get-WAPackSubscription command, specifically if the public portal address is different than the FQDN of WAP Admin Server.
  • If you are getting errors during deployment like, “Disk with Name (Windows Server 2012 Datacenter) and Version (1.0.0.0) not found while translating Resource Definition for cloud resource.” it is likely that the $OSDisk and/or $OSDiskVersion Variables have incorrect data. The Tenant API does not care so much for the actual OS Disk name, instead, it appears to require the Family Name of the OS Disk.
  • I chose to leave all the Variable settings within the script. These could very easily be presented as Script Parameters, and fed into the script to make it a bit more generic.
  • Just like the Service Admin example script, there is a portion of the Resource Definition Configuration (ResDefConfig) that is tied directly to the ResDef/ResDefExt for a given Gallery Item VM Role. When generating the Gallery Item Parameter Hashtable, I have separated the GI specific data from the common GI data. In most cases (especially for the Gallery Item VM Roles produced by my team), the only portion of the script that has to change per VM Role is this GI specific data (and, of course any specific Variable data).
  • This script (for the Tenant Admin) should look nearly identical to the script from Part 2 (for the Service Admin). This is by design, as I wanted to keep as may synergies in play as possible. Remember, there are only subtle differences (Ports and Auth).
  • While the steps in the Gallery Item VM Role deployment process will likely remain the same, the actual script could be improved in various ways: Addition of Script Parameters, Separated into Functions, Transformed into a Set of Cmdlets, etc. If anyone takes these improvements on, I will be happy to reference / endorse the published work here in this blog.
  • Finally, and once again, the script has been broken up into “regions”, each of which builds on the last, to eventually complete all the data collection / command execution for the final Invoke-WebRequest POST to deploy the Gallery Item VM Role. Here is an image illustrating the seven regions:
    image

After the dust settles on back to back executions, you should see something similar to this in the Tenant Portal:

image


Future Discovery: Modified Example PowerShell script to enumerate ResDef/ResDefExt and ResDefConfig Requirements for any Gallery Item VM Role (as a Tenant Admin against the Public WAP API)

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
function Get-GIResourceParams {

    param
    (
        [object]$Cert,
        [string]$TenantPortalAddress,
        [string]$SubscriptionID,
        [string]$GalleryItemName
    )

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

    # Get Gallery Item Resource Definition
    $GIResDEFUri = "https://{0}:30006/{1}/{2}/?api-version=2013-03" -f $TenantPortalAddress,$SubscriptionID,$GalleryItemREF
    $GIResourceDEFJSON = Invoke-WebRequest -Uri $GIResDEFUri -Certificate $Cert -UseBasicParsing | Select-Object -ExpandProperty Content 
    $ResDef = ConvertFrom-Json $GIResourceDEFJSON

    Return $ResDef.ResourceParameters.Name

}

$WAPSubscription = Get-WAPackSubscription

$CertThumb = $WAPSubscription.Certificate.Thumbprint
$CertPath = "Cert:\CurrentUser\My\{0}" -f 
$CertThumb
$Cert
 = Get-Item 
$CertPath

$TenantPortalAddress
 = $WAPSubscription.ServiceEndpoint.Host
$SubscriptionID = $WAPSubscription.SubscriptionId
$GalleryItems = @("DomainController","Lync","SharePoint","Exchange")

$GIandResourceParams = @{}

foreach ($GalleryItem in $GalleryItems)
{
    $GIResourceParams = Get-GIResourceParams -Cert $Cert -TenantPortalAddress $TenantPortalAddress `
        -SubscriptionID $SubscriptionID -GalleryItemName $GalleryItem

    $GIandResourceParams += @{$GalleryItem = $GIResourceParams}
}

$GIandResourceParams

Note     This is essentially a copy/paste of the Service Admin script, with modifications to work against the Public WAP Tenant API. The major updates: URL port (changed from 30005 to 30006); and Authorization method (changed from Header with bearer token to Certificate).

And once again, the output of this script is:

image


But wait, WHY would I want to do this?

Great question!

I assume once you watch the video (below) you will have a few ideas, but here are some use cases (from both the Service Admin and Tenant Admin personas):

  • Use Case 1: As a Tenant – Simple avoidance of manual clicking to deploy Gallery Item VM Roles
  • Use Case 2: As a Tenant – Develop scripts to fully deploy a set of multiple concurrent (and/or dependent) Gallery Item VM Role Deployments (with scripts like this, you have complete control over the “what” and “when”)
  • Use Case 3: As a Service Provider (or Enterprise acting like one) – Create a custom set of cmdlets encapsulating the parameters and logic into easily consumable/executable commands
  • Use Case 4: As a Service Provider (or Enterprise acting like one) – Enabling your Tenants/End Users to automate their own Gallery Item VM Role deployments (external to any SMA efforts on the Service Admin side)

Note     Again, 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.


So that’s a wrap, right?

Oh, you want the VIDEO and TECHNET GALLERY CONTRIBUTION now, do you?

Okay.


Automated Tenant Provisioning, the 8-Minute-Demo Video!

Fun Fact     It may be my first 8-Minute-Demo Video that is actually 8 minutes exactly.


TechNet Gallery Contribution and Download

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

For the Service Administrator

SMA Runbook Exports

  • Create-VMNetwork.xml
  • Deploy-TenantVMRole.xml
  • Deploy-VMRole.xml
  • Subscription-Create-Dispatcher.xml
  • VMRole-Create-Dispatcher.xml

PowerShell Scripts

  • Deploy-VMRole_OptionalVMMCommands.ps1
  • Get-GIResourceParams_asServiceAdmin.ps1

PowerShell Workflows

  • Create-VMNetwork.ps1
  • Deploy-TenantVMRole.ps1
  • Deploy-VMRole.ps1
  • Subscription-Create-Dispatcher.ps1
  • VMRole-Create-Dispatcher.ps1

For the Tenant Administrator

PowerShell Scripts

  • Deploy-TenantVMRoles_asTenantAdmin.ps1file.ps1
  • Get-GIResourceParams_asTenantAdmin.ps1

Note     XML (SMA Runbooks) and PS1 (PowerShell Scripts) files are both provided in the download. Use SMART for Runbook Import and Export to leverage the provided XML files in the above download for an enhanced experience in importing the example solution into your SMA environment.

Optional     Some of the scripts within this download contain commented out “optional” portions for Monitoring and Notifications. The associated Runbooks and Variables for these options are not included in this download. For more information about Monitoring and Notifications within SMA, please see the following blog post: Automation–Monitoring and Notifying in Windows Azure Pack with SMA


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

BC-DLButtonDark


Oh, and have you seen this blog post yet?

Windows Azure Pack–Gallery Item VM Role–References for Creation, Configuration, and Automation

If not, check it out.

And while it does cross-reference back to this post, it covers the entire Gallery Item VM Role Lifecycle (well the important bits, anyway).


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)

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!