The New World of Tenant Provisioning Published: 2014 For the latest information, please see http://aka.ms/BuildingClouds Introduction ..........................................................................................................................................................1 So, what is this white paper all about? ................................................................................................. 1 Background ...........................................................................................................................................................2 Looking Back .................................................................................................................................................... 2 What can we salvage? .................................................................................................................................. 2 What’s new? ..................................................................................................................................................... 2 Download ...............................................................................................................................................................3 Automated Deployment of Tenant Network and Identity Workload...........................................4 Automated Tenant Virtual Network Deployment ............................................................................ 4 Create a VM Network “OnBehalfOf” a User Role ............................................................................. 5 Automated Active Directory VMRole Deployment .......................................................................... 7 The Options ...................................................................................................................................................... 7 The Process ....................................................................................................................................................... 8 Even More Magic – Bearer Tokens ....................................................................................................... 10 The overall structure of the SMA Runbooks .................................................................................... 11 Deploy a Gallery Item VMRole ............................................................................................................... 13 BONUS - Example VMM PowerShell for Gallery Item VMRole Deployment!..................... 20 Use Cases ........................................................................................................................................................ 21 Automated Deployment of the Identity Workload as a Tenant Admin ................................... 22 The Options .................................................................................................................................................... 23 The Process ..................................................................................................................................................... 24 The Pre-Requisites ....................................................................................................................................... 25 The Pre-Requisite Setup ........................................................................................................................... 26 Active Directory Gallery Item VMRole Deployment as a Tenant Admin .............................. 27 Use Cases ........................................................................................................................................................ 31 Automated Deployment of Tenant Workloads (Lync, SharePoint, and Exchange).............. 32 The Scope........................................................................................................................................................ 33 Out of Scope .................................................................................................................................................. 34 Deployment as a Service Administrator in SMA (with the WAP Tenant API Only) .......... 34 Existing: SMA Runbook that is the same for each Gallery Item VMRole Deployment ... 34 New: Updated SMA Runbooks… ........................................................................................................... 35 The New World of Tenant Provisioning …for the Lync Gallery Item VMRole Deployment ........................................................................... 36 …for the SharePoint Gallery Item VMRole Deployment .............................................................. 36 …for the Exchange Gallery Item VMRole Deployment ................................................................. 36 Future Discovery: How to enumerate ResDef/ResDefExt and ResDefConfig Requirements for any Gallery Item VMRole ..................................................................................... 38 Deployment as a Tenant Administrator from a PowerShell Script (with the Public WAP Tenant API) ..................................................................................................................................................... 40 New: Deployment Script for Active Directory, Lync, Exchange, or SharePoint .................. 40 Future Discovery: Modified Example PowerShell script to enumerate ResDef/ResDefExt and ResDefConfig Requirements for any Gallery Item VMRole ............................................... 44 Use Cases ........................................................................................................................................................ 45 Appendix ............................................................................................................................................................. 46 Links Referenced within the Document.............................................................................................. 46 Links from the Related Blog Series ....................................................................................................... 47 Links to Other Related and Valuable Content ................................................................................. 47 The New World of Tenant Provisioning Introduction To be clear, this document is all about [on-prem] Automated Tenant Provisioning with [Windows] Azure Pack (WAP). And WAP is just one of the primary technologies leveraged within this example. In fact, Service Management Automation (SMA), PowerShell, PowerShell Workflow, Virtual Machine Manager (VMM), and VMRole Gallery Items, are the backbone of the [example] solution. Note The guidance here was previously published blog content found on the Building Clouds Blog. So, what is this white paper all about? 1 Applying existing knowledge of tenant provisioning techniques in this “new world” Transforming existing knowledge (Service Templates) of tenant workload deployments into “Azure Pack friendly” workload deployments (VMRoles) Providing new automation (PowerShell scripts) for the latest tenant provisioning technology (Azure Pack) The New World of Tenant Provisioning Background Looking Back If you have been following my work on the Building Clouds Blog, you may remember the following two blog posts: Automation–PowerShell Workflow Script Spotlight–Deploying Virtual Machine Manager Service Templates “OnBehalfOf” Tenant Administrator User Roles Automation–PowerShell Workflow Script Spotlight–Creation and Parameterization of Virtual Machine Manager Run As Accounts for “OnBehalfOf” Service Template Deployment Well, those were directly related to the initial learning around Tenant Provisioning my team gathered during an internal Proof of Concept. Back then, it was all about the “VMM Service Template”, and while some of that existing knowledge will work in this new world of Azure Pack, the focus now is on VMRoles as the delivery mechanism for application workloads. What can we salvage? At the very least, all the existing knowledge around automated deployment of the Tenant Virtual Network (Isolated Software Defined Network (SDN)). As well as, some previously undisclosed techniques for automatically initiating the deployments - all of which still holds true, regardless of delivery mechanism. What’s new? The new automation involved in the deployment of VMRoles, as both a Service Admin (via VMM “OnBehalfOf” PS Commands) and a Tenant Admin (via the Service Management WS API). 2 The New World of Tenant Provisioning Download The download for all the artifacts within this example solution can be found on TechNet Gallery. The downloadable content introduces a collection of example PowerShell Scripts and SMA Runbooks that you can use to Automate Tenant Provisioning of Gallery Item VMRoles within WAP. URL: http://gallery.technet.microsoft.com/Windows-Azure-Pack-Tenant-3e8afd64 The download (Windows Azure Pack Tenant Provisioning Automation Toolkit.zip) includes (14) files: For the Service Administrator SMA Runbook Exports (5 files) o Create-VMNetwork.xml o Deploy-TenantVMRole.xml o Deploy-VMRole.xml o Subscription-Create-Dispatcher.xml o VMRole-Create-Dispatcher.xml PowerShell Scripts (2 files) o Deploy-VMRole_OptionalVMMCommands.ps1 o Get-GIResourceParams_asServiceAdmin.ps1 PowerShell Workflows (5 files) o Create-VMNetwork.ps1 o Deploy-TenantVMRole.ps1 o Deploy-VMRole.ps1 o Subscription-Create-Dispatcher.ps1 o VMRole-Create-Dispatcher.ps1 For the Tenant Administrator PowerShell Scripts (2 files) o Deploy-TenantVMRoles_asTenantAdmin.ps1 o 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 3 The New World of Tenant Provisioning Automated Deployment of Tenant Network and Identity Workload What does that mean, exactly? Well for the context of this document, 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 VMRole. So, for organization’s sake, this section of the document will be split into two main sections – one for automated Tenant Virtual Network deployment, and one for automated VMRole 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 the blog. That said, for this document, 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: 4 The New World of Tenant Provisioning The reason I believe this is important, is that I will be referring to each persona throughout the document. 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. 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 5 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} The New World of Tenant Provisioning 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 $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 6 #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 The New World of Tenant Provisioning 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. URL: 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. Automated Active Directory VMRole Deployment The Options In fact, this is another great time to illustrate this in image form, for these two personas: 7 The New World of Tenant Provisioning Note These options are the same for any Automated VMRole Deployment, Active Directory happens to be the first one to be deployed, so it gets all the attention. The Process I believe it is important for everyone to understand the current step-by-step process necessary to automatically deploy a VMRole via PowerShell against the available endpoints. It will let you appreciate the script that much more. 1. Generate the Gallery Item VMRole Reference URI (based on Subscription ID and Tenant Portal Address) 2. Invoke-WebRequest to Get the Gallery Item VMRole Reference (portion of the Gallery Item VMRole Resource Definition (ResDef) URI specific to the Gallery Item VMRole, based on the Gallery Item VMRole Name and data returned from Step #1) 3. Generate the Gallery Item VMRole ResDef URI (based on data returned from Step #2) 4. Invoke-WebRequest to Get the Gallery Item VMRole 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 VMRole Parameter Hashtable (based on custom variable data) 7. Convert the Gallery Item VMRole Parameter Hashtable to JSON 8. Create the Gallery Item VMRole Resource Definition Configuration (ResDefConfig) 'System.Collections.Generic.Dictionary[String,Object]' (based on converted Gallery Item VMRole Parameter data (JSON) and Version information) 9. Create the Gallery Item VMRole Payload Hashtable (based on custom variable data, Gallery Item VMRole ResDef and ResDefConfig Dictionary Objects) 10. Convert the Gallery Item VMRole Payload Hashtable to JSON 11. Verify/Create Cloud Service Name (based on custom variable data) 12. Generate the Gallery Item VMRole Deployment URI (based on Subscription ID, Tenant Portal Address, and Cloud Service Name) 8 The New World of Tenant Provisioning 13. Invoke-WebRequest to Post the Gallery Item VMRole Payload JSON (based on the URI from Step #14) Step #11 (Cloud Service Creation/Verification) has several Sub-Steps: 1. 2. 3. 4. 5. 6. 7. Generate the Cloud Service URI (based on Subscription ID and Tenant Portal Address) Invoke-WebRequest to Get the Cloud Service Data and verify if Cloud Service already exists (based on the URI from Sub-Step #1) If exists, Output Cloud Service Name If notexists, Create the Cloud Service Parameter Hashtable (based on custom variable data) Convert the Cloud Service Parameter Hashtable to JSON Invoke-WebRequest to Post the Cloud Service JSON (based on the URI from Sub-Step #1) Once created, Output Cloud Service Name Some people like images better, so here is one that represents the text above: 9 The New World of Tenant Provisioning 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 VMRole 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 VMRoles Tenant API Even More Magic – Bearer Tokens 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 InvokeWebRequest is made against the WAP Tenant API (non-Public). In fact, this bit of script is 10 The New World of Tenant Provisioning 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, 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 VMRole 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). 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. The overall structure of the SMA Runbooks 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 VMRole Deployments – this is where multiple VMRole Deployments can be called at once) 11 The New World of Tenant Provisioning 2. 3. 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 Deploy-VMRole: Lowest Tier Sub-Runbook; called by Middle Tier SubRunbook; collects and uses input parameter data; executes all required VMRole Deployment calls (this is where some of the “options” come in – for example, instead of executing only against the WAP Tenant API, the actual VMRole Deployment could execute the required VMM Cmdlet command (AddCloudResource) And to help with the overall visualization as well as where these SMA Runbooks fit in the all-up process, here is another 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. 12 The New World of Tenant Provisioning Deploy a Gallery Item VMRole I am going to start with the Lowest Tier Sub-Runbook (Deploy-VMRole), and work up. The following PowerShell workflow script (Deploy-VMRole) will deploy a WAP Gallery Item VMRole 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 VMRoles will be deployed, defined with parameter> User ID: <User ID of the User for whom the VMRoles will be deployed, defined with parameter> Gallery Item Name: <Partial or Full Name of the Gallery Item VMRole 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 VMRole to be Deployed, defined with parameter, used to identify correct version of the Gallery Item VMRole> ResDefConfig JSON: <Resource Definition Configuration data (in JSON format), defined with parameter, used to satisfy the ResDef, part of the Gallery Item VMRole Payload> Cloud Service Name: <Name of the Cloud Service, defined with parameter, used in the Gallery Item VMRole Deployment> VMRole Name: <Name of the VMRole to be Deployed, defined with parameter, part of the Gallery Item VMRole 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 SubRunbook 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 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 { 13 The New World of Tenant Provisioning 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 $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 VMRole 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 VMRole $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. 14 The New World of Tenant Provisioning Calling the Deploy-VMRole PowerShell workflow The following PowerShell workflow script (Deploy-TenantVMRole) will call the DeployVMRole 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 VMRoles will be deployed, defined by parsing the $OwnerUserRole parameter> User ID: <User ID of the User for whom the VMRoles will be deployed, defined by parsing the $OwnerUserRole parameter> Gallery Item Name: <Partial or Full Name of the Gallery Item VMRole to be Deployed, defined by parsing the $GalleryItemToDeploy parameter> Gallery Item Version: <Version of the Gallery Item VMRole 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 VMRole Deployment, defined by SMA Variable> Password: <Password used in the Gallery Item VMRole Deployment, defined by SMA Variable> ResDefConfig JSON: <Resource Definition Configuration data (in JSON format), defined by numerous parameters/variables, contains common and Gallery Item VMRole 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 VMRole specific data so that the line between them is clear and updates are simple (as the number of Gallery Item VMRoles 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 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' 15 The New World of Tenant Provisioning 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 $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 VMRole 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 VMRole. So, you may have actually missed where this went from “generic” to “specific”. If you did, look up at the Deploy-TenantVMRole workflow script again, and checkout 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 SubRunbook 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 VMRole specific data (as it relates to the other Gallery Item VMRoles in this document, 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 VMRole 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 VMRole 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. 16 The New World of Tenant Provisioning 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 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> VMRole Name: <Name of the VMRole to be Deployed (will be how the VMRole is seen in the Tenant Portal), defined by custom data> VMRole 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> VMRole Size: <Size of the VMRole to be Deployed, restricted to available VMM Hardware Profile/Gallery Item VMRole 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 VMRole 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 VMRole 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: It is the main “hook” for the Tenant Provisioning Process of “Subscribe to a Plan, Get a Fully Deployed Set of Collaborative Workloads”. 17 The New World of Tenant Provisioning 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 VMRole 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. 18 The New World of Tenant Provisioning 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-CreateDispatcher. And because the Deploy-VMRole workflow leverages the same exact method for VMRole creation as the Tenant Portal, the WAP/SPF event for MicrosoftCompute.VMRole works the same: 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 Monitoring and Notifications allow for the creation of emails like: 19 The New World of Tenant Provisioning BONUS - Example VMM PowerShell for Gallery Item VMRole Deployment! 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 20 The New World of Tenant Provisioning 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). Use Cases But wait, WHY would I want to do this? 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 VMRole deployment from a custom selfservice portal, feeding the SMA Runbooks with more dynamic parameter data Use Case 3: Integrate Gallery Item VMRole 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 VMRoles on-demand 21 The New World of Tenant Provisioning Automated Deployment of the Identity Workload as a Tenant Admin I realize the heading of this section is very similar to the last, and there is very good reason for that. Though they look similar, they are different. Different personas; different scripts; different context. The scope of this section is illustrated by the portion of the image with the red outline: To clear things up, let’s expand on the title for this section, (now with more sub-title!): Automated Deployment of the Identity Workload as a Tenant Admin (Active Directory VMRole; from the Tenant Admin Persona) The last section of this document was all about deployment as the Service Administrator persona. Now, in this section, it is all about deployment as the Tenant Administrator. I think it is time for another quick rehash (with image): 22 The New World of Tenant Provisioning In the above image, we see my view of these two personas, as well as what we will be getting into for this post (the first two bullets in the box on the right). The Options How about another image? This time, to visualize the high-level process for the Tenant Admin: So, as you can see, the Tenant Admin has options. Note Automatically adding the Isolated Tenant Virtual Network (Isolated Software Defined Network (SDN)) is completely optional, as the Service Admin can simply recommend that the Tenant Admin manually create Virtual Networks from the Tenant Portal before deploying VMRoles. Reminder These options are the same for any VMRole Deployment, Active Directory happens to be the first one to be deployed, so it gets all the attention. 23 The New World of Tenant Provisioning The Process Remember that multi-step process I described in the last section? Well, it is basically the same. So, to save you from going back and forth, here is the representative image for “The Process”: What’s the difference? Primarily, it is the Web Service Endpoint PORT and Authorization method. As a Service Admin, you can leverage Bearer Token authorization and make WS calls against the Tenant API (non-Public), Port 30005 As a Tenant Admin, you leverage Certificates and make WS calls against the Public Tenant API, Port 30006 In fact, if you are familiar with Automating Windows Azure with PowerShell, the prerequisite process has a very similar look and feel. Care to venture a guess what we leverage to setup/use the Certificate? 24 The New World of Tenant Provisioning The Windows Azure PowerShell Module (It is one of the options anyway.) That’s right. The very same – and if you haven’t checked it out, or downloaded the latest version, you may have missed all the “new-ish” commands that contain *-WAPack* Which is a great segue to… The Pre-Requisites 1. Valid Account and Subscription to a Plan within WAP (have the ability to login to the WAP Tenant Portal and provision Gallery Items (VMRoles)): 2. The latest Windows Azure PowerShell module (contains the latest *-WAPack* Commands): 3. Ability to Execute Windows Azure PowerShell Commands 25 The New World of Tenant Provisioning 4. Creation and Usage of a Management Certificate (derived from WAP Publish Settings File – see the Pre-Requisite Setup steps below) The Pre-Requisite Setup 1. 2. Download and Install the Windows Azure PowerShell Commands Get the Published Settings File for your Subscription – Navigate to your version of the following URL: https://tenant.portal.com:30081/publishsettings This will prompt to download the file locally – My recommendation is to download to the machine where the PowerShell Scripts will be executed. Note If the target portal leverages ADFS, do not include the port (30081) in the address while attempting to retrieve the .publishsettings file. At least this is what I experienced between the two environments we have, one without ADFS (required port in URL), and one with ADFS (port not used in URL). (Example Path: C:\LocalPath\SubscriptionName-MM-d-YYYYcredentials.publishsettings) 3. 4. Open PowerShell and ensure the Windows Azure PowerShell module is loaded Execute the following command (modify the command text based on the name and where the publishsettings file is located): Import-WAPackPublishSettingsFile C:\WAP\Collaboration Workloads (Tenant Deployed)-3-11-2014-credentials.publishsettings 26 The New World of Tenant Provisioning Note These steps not only import the certificate into the Management Certificates section in the Tenant Portal: But ALSO add it to the Certificate Store for the Current User: Resulting in the following, when Get-WAPackSubscription is executed from PowerShell: Reminder The security best practice for the publishsettings file is to store it temporarily, and then delete it after the settings have been imported. A malicious user gaining access to the publishsettings file can edit, create, and delete your WAP resources. So, there you go - All the theory and pre-requisites you can handle, right? The good news…With these pre-requisites out of the way, we can now get on to the actual deployment script! Active Directory Gallery Item VMRole Deployment as a Tenant Admin (Against the Public WAP API) 001 002 003 #region GetWAPConnectionData # Get WAP Subscription Information 27 The New World of Tenant Provisioning 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 $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 = "DomainController" $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 $VMRoleName = "ActiveDirectory" $VMRoleNamePattern = "DC##" $VMRoleSize = "ExtraSmall" $VMRoleTZ = "Pacific Standard Time" $OSDisk = "Windows Server 2012 Datacenter" $OSDiskVersion = "1.0.0.0" $Password = "Password" #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} } # 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 28 The New World of Tenant Provisioning 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 # 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 VMRole Deploy URI $GIDeployUri = "https://{0}:{1}/{2}/CloudServices/{3}/Resources/MicrosoftCompute/VMRoles/?api-version=2013-03" -f $TenantPortalAddress,$Port,$SubscriptionID,$CloudService # Deploy Gallery Item VMRole $VMRoleDeployed = Invoke-WebRequest -Uri $GIDeployUri -Certificate $Cert -Method Post -Body $GIPayloadJSON -ContentType "application/json" $VMRoleDeployed #endregion DeployGIVMRole Note(s) I have several notes about the above script. So I will list them here: This is just an example script. It has been tested against our Demo/Test/Dev environment multiple times. It absolutely requires the pre-requisites discussed above. And while there are alternatives to getting the required variable data based on the GetWAPackSubscription 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 GetWAPackSubscription 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. 29 The New World of Tenant Provisioning 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 VMRole. 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 VMRoles produced by my team), the only portion of the script that has to change per VMRole 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 many synergies in play as possible. Remember, there are only subtle differences (Ports and Auth). While the steps in the Gallery Item VMRole 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 document. Finally, as you can see, I have broken the script 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 VMRole. Here is an image illustrating the seven regions: All that said, if everything goes right during the execution of this example script, you should see something very similar to this: 30 The New World of Tenant Provisioning Use Cases But wait, WHY would I want to do this? 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 VMRoles Use Case 2: As a Tenant – Develop scripts to fully deploy a set of multiple concurrent (and/or dependent) Gallery Item VMRole 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 VMRole deployments (external to any SMA efforts on the Service Admin side) And with that, we can do another status check on the scope of the document so far: 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 VMRole. This was due to the fact that the subsequent example Gallery Item VMRole deployments in this document take a strong dependency on Active Directory. In this way, both the Gallery Item VMRole for Active Directory and the Tenant Network are “table stakes”. So, at this point, based on this and the previous section of this document, you really have everything necessary to deploy not only the Gallery Item VMRole for Active Directory, but any Gallery Item VMRole you have built (or pulled down off of WebPI (or the Building Clouds Blog)). But hey, I am not going to leave you hanging. The very next section is all about Tenant Workloads, where I will be providing the necessary script updates (to what you have seen so far) to deploy the Gallery Item VMRoles for Lync, SharePoint, and Exchange (for both personas). 31 The New World of Tenant Provisioning Automated Deployment of Tenant Workloads (Lync, SharePoint, and Exchange) This section of the document is all about describing what it takes and providing the necessary script updates (to what you have seen so far) to deploy the Gallery Item VMRoles for Lync, SharePoint, and Exchange (for both personas). Which means, at least in part, these (previously seen) three diagrams happen to also apply to this section: Automated Deployment of Tenant Workloads (Lync, SharePoint, and Exchange VMRoles; from both Service Admin and Tenant Admin Personas) Because we established a solid foundation in the previous section of the document, this section really just highlights the script updates necessary to get Automated WAP Deployments to work for OUR PUBLISHED Lync, SharePoint, and Exchange Gallery Item 32 The New World of Tenant Provisioning VMRoles. The reason I say “OUR PUBLISHED”, in yelling-case, is to emphasize which Gallery Item VMRoles the example scripts in this section actually directly relate. If you are wondering, “OUR PUBLISHED” Lync, SharePoint, and Exchange Gallery Item VMRoles can be found here: Windows Azure Pack VMRole Gallery Items for Collaboration Workloads Wait! Wait! Wait! What’s the difference in the script from VMRole to VMRole? Well, as you saw in the previous sections of the document, no one generic script can be created that handles the deployment from beginning to end. This is due to the fact that each VMRole Definition can vary, based on how that VMRole 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 VMRole type. Will I be able to apply what I learn here against the VMRoles 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 the “Automated Tenant Virtual Network Deployment” section of this document for more information and example script. Note The TechNet Gallery Contribution download also includes the example script(s) for automatically creating a Tenant Virtual Network. The Scope Just like in the previous two sections, there are various options to choose from when deploying Gallery Item VMRoles. These options are depicted above (the one with all the green checkmarks). To keep this section 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 VMRole Deployment New: Updated SMA Runbooks for the Lync Gallery Item VMRole Deployment New: Updated SMA Runbooks for the SharePoint Gallery Item VMRole Deployment New: Updated SMA Runbooks for the Exchange Gallery Item VMRole Deployment Future Discovery: How to enumerate ResDef/ResDefExt and ResDefConfig Requirements for any Gallery Item VMRole 33 The New World of Tenant Provisioning 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 VMRole 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 VMRole (as a Tenant Admin against the Public WAP API) Out of Scope There are a couple topics out of scope for this particular section, as they have been covered previously within the document: 1. 2. 3. 4. 5. 6. Automated Tenant Virtual Network Creation Automated Active Directory Gallery Item VMRole Deployment Detailed Review of “The Process” for constructing a Gallery Item VMRole Deployment Script Example VMM PowerShell for Gallery Item VMRole Deployment (“AddCloudResource”) Pre-Requisites for WAP Tenant API Authentication (“bearer token authorization”) Pre-Requisites for Public WAP Tenant API Authentication (“Windows Azure PowerShell Module” and “certificates”) In other words, from a deployment perspective (and from the Tenant’s view), this is our starting point: Deployment as a Service Administrator in SMA (with the WAP Tenant API Only) Existing: SMA Runbook that is the same for each Gallery Item VMRole Deployment If we take a look at the “overall structure of the SMA Runbooks” section above, the following SMA Runbook will be the same for each Gallery Item VMRole Deployment 34 The New World of Tenant Provisioning (well, depending on your implementation, but definitely for this example) and can be leveraged generically: For the example script reference here, refer to the “Example PowerShell workflow script for Deploy-VMRole” section above. 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 VMRole Deployment: Note I provide the updated SMA Runbook examples for each Tenant Workload Deployment below. But 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 will concentrate more on it, rather than the Subscription-CreateDispatcher SMA Runbook. Remember in both previous sections 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: 35 The New World of Tenant Provisioning 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 VMRole type. Note I will be including the script portions in each respective Tenant Workload subsection 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-CreateDispatcher SMA Runbook with calls for all 4 (including Active Directory) Tenant Workload Deployments. …for the Lync Gallery Item VMRole 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 VMRole Deployment 01 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 VMRole 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. Options You may want to move some of what I have deemed as “Common Data” to these “GI Specific Data” Hashtables. A good example of this is “VMRoleOSVirtualHardDiskImage = $OSDisk”. Not every VMRole will leverage the same “Common” OS Disk, so to assign different OS Disks to different VMRoles, all you have to do is move the $OSDisk variable assignment to the “GI Specific” Hashtable(s). 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 let me know, or send a link to your related blog post. I am always happy to cross-post. 36 The New World of Tenant Provisioning 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 VMRole 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 VMRoles 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 VMRole 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 below! 37 The New World of Tenant Provisioning 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 VMRole 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 (nonPublic), pulls down the specified Gallery Item VMRoles desired for comparison, and 38 The New World of Tenant Provisioning outputs a Hashtable containing the Gallery Item VMRole 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 VMRole Resource Parameters: 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! 39 The New World of Tenant Provisioning Deployment as a Tenant Administrator from a PowerShell Script (with the Public WAP Tenant API) New: Deployment Script for Active Directory, Lync, Exchange, or SharePoint Example PowerShell script for Active Directory, Lync, Exchange, or SharePoint VMRole Deployment (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 #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 40 The New World of Tenant Provisioning 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 $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 41 The New World of Tenant Provisioning 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 "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 VMRole Deploy URI $GIDeployUri = "https://{0}:{1}/{2}/CloudServices/{3}/Resources/MicrosoftCompute/VMRoles/?api-version=2013-03" -f $TenantPortalAddress,$Port,$SubscriptionID,$CloudService # Deploy Gallery Item VMRole $VMRoleDeployed = Invoke-WebRequest -Uri $GIDeployUri -Certificate $Cert -Method Post -Body $GIPayloadJSON -ContentType "application/json" $VMRoleDeployed #endregion DeployGIVMRole Note(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 VMRoles included within the script: The logic is all based on the included IF blocks (two per Gallery Item VMRole). and It absolutely requires the pre-requisites discussed in Part 3. And while there are alternatives to getting the required variable data based on the GetWAPackSubscription 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 GetWAPackSubscription 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 42 The New World of Tenant Provisioning $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 VMRole. 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 VMRoles produced by my team), the only portion of the script that has to change per VMRole 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 many synergies in play as possible. Remember, there are only subtle differences (Ports and Auth). While the steps in the Gallery Item VMRole 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 document. 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 VMRole. Here is an image illustrating the seven regions: After the dust settles on back to back executions, you should see something similar to this in the Tenant Portal: 43 The New World of Tenant Provisioning Future Discovery: Modified Example PowerShell script to enumerate ResDef/ResDefExt and ResDefConfig Requirements for any Gallery Item VMRole Modified Example PowerShell script for Get-GIResourceParams (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: 44 The New World of Tenant Provisioning Use Cases But wait, WHY would I want to do this? 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 VMRoles Use Case 2: As a Tenant – Develop scripts to fully deploy a set of multiple concurrent (and/or dependent) Gallery Item VMRole 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 VMRole deployments (external to any SMA efforts on the Service Admin side) 45 The New World of Tenant Provisioning Appendix The following are the links referenced throughout this document as well as links related directly to the content: Links Referenced within the Document Building Clouds Blog: http://aka.ms/BuildingClouds Windows Azure Pack Tenant Provisioning Automation Toolkit TechNet Gallery Contribution: http://gallery.technet.microsoft.com/Windows-Azure-PackTenant-3e8afd64 VMNetworks Collection API Documentation on MSDN: http://msdn.microsoft.com/en-us/library/dn765986.aspx Automation–PowerShell Workflow Script Spotlight–Deploying Virtual Machine Manager Service Templates “OnBehalfOf” Tenant Administrator User Roles: http://blogs.technet.com/b/privatecloud/archive/2013/10/24/automationpowershell-workflow-script-spotlight-deploying-virtual-machine-managerservice-templates-onbehalfof-tenant-administrator-user-roles.aspx Automation–PowerShell Workflow Script Spotlight–Creation and Parameterization of Virtual Machine Manager Run As Accounts for “OnBehalfOf” Service Template Deployment: http://blogs.technet.com/b/privatecloud/archive/2013/10/30/automationpowershell-workflow-script-spotlight-creation-and-parameterization-of-virtualmachine-manager-run-as-accounts-for-onbehalfof-service-templatedeployment.aspx SMART for Runbook Import and Export: http://blogs.technet.com/b/privatecloud/archive/2013/10/23/automationservice-management-automation-sma-runbook-toolkit-spotlight-smart-forrunbook-import-and-export.aspx Automation–Monitoring and Notifying in Windows Azure Pack with SMA: http://blogs.technet.com/b/privatecloud/archive/2014/01/30/automationmonitoring-and-notifying-in-windows-azure-pack-with-sma.aspx Bearer Token Bing Search: http://www.bing.com/search?q=bearer+token+authorization&qs=n&form=QB RE&pq=bearer+token+authorization TechNet Documentation about Get-MgmtSvcToken: http://technet.microsoft.com/en-us/library/dn520915(v=sc.20).aspx Using automation with Virtual Machine Clouds: http://technet.microsoft.com/enus/library/dn457800.aspx VMRoles Tenant Service Documentation on MSDN: http://msdn.microsoft.com/en-us/library/dn502556.aspx 46 The New World of Tenant Provisioning TechNet Documentation about Add-CloudResource: http://technet.microsoft.com/en-us/library/dn472838(v=sc.20).aspx Automation–Automating Hybrid Clouds with Windows Azure and PowerShell (Part 2): Public Cloud Environment Provisioning PowerShell Examples: http://blogs.technet.com/b/privatecloud/archive/2013/11/19/automationautomating-hybrid-clouds-with-windows-azure-and-powershell-part-2-publiccloud-environment-provisioning-powershell-examples.aspx How to install and configure Azure PowerShell: http://www.windowsazure.com/en-us/documentation/articles/install-configurepowershell/ Microsoft Azure CMD Line Tools: http://www.windowsazure.com/enus/downloads/#cmd-line-tools Windows Azure Pack VMRole Gallery Items for Collaboration Workloads: http://blogs.technet.com/b/privatecloud/archive/2013/12/11/building-cloudsblog-windows-azure-pack-vmrole-gallery-items.aspx Links from the Related Blog Series Automation–The New World of Tenant Provisioning with Windows Azure Pack Part 1: Intro & TOC Part 2: Automated Deployment of Tenant Network and Identity Workload (Isolated Tenant Virtual Network & Active Directory VMRole; from the Service Admin Persona) Part 3: Automated Deployment of the Identity Workload as a Tenant Admin (Active Directory VMRole; from the Tenant Admin Persona) Part 4: Automated Deployment of Tenant Workloads (Lync, SharePoint, and Exchange) (Lync, SharePoint, and Exchange VMRoles; from both Service Admin and Tenant Admin Personas) Part 5: Working with the SQL Server resource provider, and the ITIL dilemma (by Bruno Saille) Links to Other Related and Valuable Content Windows Azure Pack–Gallery Item VMRole–References for Creation, Configuration, and Automation: http://blogs.technet.com/b/privatecloud/archive/2014/03/11/windows-azurepack-gallery-item-vm-role-resources-for-creation-configuration-andautomation.aspx 8-Minute-Demo: Automated Tenant Provisioning Video: http://www.youtube.com/watch?v=xZIgPAyyg7E 47 The New World of Tenant Provisioning