0004-New-AzureRmVmAvailabilitySetPsg.ps1
<#PSScriptInfo
.VERSION 1.1.0.10 .GUID 01c58ba7-37f8-40de-98fb-1495ea5b27dd .AUTHOR Preston K. Parsard .COMPANYNAME Microsoft .COPYRIGHT Copyright (c) 2016 Preston K. Parsard .TAGS Azure, Deploy, VM .LICENSEURI https://opensource.org/licenses/MIT .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES WriteToLogs; https://www.powershellgallery.com/packages/WriteToLogs .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES 17 OCT 2016 1.1.0.0 Preston K. Parsard Updated description and changed versioning format from ##.##.#### to #.#.#.# to match the standard module versioning convention format. 18 OCT 2016 1.1.0.1 Preston K. Parsard Created log file earlier in script, added the Start-Transcript feature that records a transcript in a separate file, and created a new logging directory name of "New-AzureRmAvSet". 18 OCT 2016 1.1.0.2 Preston K. Parsard Prompt user for location to offer more flexibility instead of hard-coding in script 18 OCT 2016 1.1.0.3 Preston K. Parsard Prompts user to open both $Log and $Transcript files upon completion of script. 19 OCT 2016 1.1.0.4 Preston K. Parsard Updated the image name to reflect the new 2016-Datacenter image. 20 OCT 2016 1.1.0.5 Preston K. Parsard Fixed regular expression for resource group naming standard to comply with the format rg##, where ## represents a two digit numeric sequence. 20 OCT 2016 1.1.0.6 Preston K. Parsard Simplified regular expression to restrict attendee number input to 4 digits. 20 OCT 2016 1.1.0.7 Preston K. Parsard Simplified regular expression to restrict site code input to 3 letters. 20 OCT 2016 1.1.0.8 Preston K. Parsard Removed exit statement at end so that the ISE/PowerShell console will still be available after the script executes. 20 OCT 2016 1.1.0.8 Preston K. Parsard Added #Requires tags for Version 5.0 and RunAsAdministrator, since the Start/Stop-Transcript cmdlets are used and administrative permissions are required for the package installer. 20 OCT 2016 1.1.0.10 Preston K. Parsard Upgraded VM image size from Standard_A1 to Standard_D1_v2. .DESCRIPTION This script creates an availability set of 1-4 Windows Server 2016 VMs with Network Security Groups for RDP access. In this script, these machines will be deployed with using the ABC[DC]## convention, where ABC is the 3 letter airport code of the location, DC indicates that these machines can subsequently be configured as domain controllers, and ## represents the sequence numbers, i.e. 01, 02, etc. Please note that this script was not designed to run as an Azure Automation Workflow Script, so if the "Deploy to Azure Automation" button is used, the script will be added to the automation account as a workflow item, but should not be expected to execute as a workflow script, but can be launched directly from a jump VM in Azure as a native PowerShell script if desired. Azure resources will be created as part of an initial process of building a functional environment consisting of compute, storage and netorking components. Since this script will be used primarily for demonstration purposes, additional comments, logging and verbose console output have been included. #> <# #Requires -Version 5.0 #Requires -RunAsAdministrator #> #> <# **************************************************************************************************************************************************************************** REFERENCES: 1. https://gallery.technet.microsoft.com/scriptcenter/Build-AD-Forest-in-Windows-3118c100 2. http://blogs.technet.com/b/heyscriptingguy/archive/2013/06/22/weekend-scripter-getting-started-with-windows-azure-and-powershell.aspx 3. http://michaelwasham.com/windows-azure-powershell-reference-guide/configuring-disks-endpoints-vms-powershell/ 4. http://blog.powershell.no/2010/03/04/enable-and-configure-windows-powershell-remoting-using-group-policy/ 5. http://azure.microsoft.com/blog/2014/05/13/deploying-antimalware-solutions-on-azure-virtual-machines/ 6. http://blogs.msdn.com/b/powershell/archive/2014/08/07/introducing-the-azure-powershell-dsc-desired-state-configuration-extension.aspx 7. http://trevorsullivan.net/2014/08/21/use-powershell-dsc-to-install-dsc-resources/ 8. http://blogs.msdn.com/b/powershell/archive/2014/07/21/creating-a-secure-environment-using-powershell-desired-state-configuration.aspx 9. http://blogs.technet.com/b/ashleymcglone/archive/2015/03/20/deploy-active-directory-with-powershell-dsc-a-k-a-dsc-promo.aspx 10.http://blogs.technet.com/b/heyscriptingguy/archive/2013/03/26/decrypt-powershell-secure-string-password.aspx 11.http://blogs.msdn.com/b/powershell/archive/2014/09/10/secure-credentials-in-the-azure-powershell-desired-state-configuration-dsc-extension.aspx 12.http://blogs.technet.com/b/keithmayer/archive/2014/10/24/end-to-end-iaas-workload-provisioning-in-the-cloud-with-azure-automation-and-powershell-dsc-part-1.aspx 13.http://blogs.technet.com/b/keithmayer/archive/2014/07/24/step-by-step-auto-provision-a-new-active-directory-domain-in-the-azure-cloud-using-the-vm-agent-custom-script-extension.aspx 14.https://blogs.msdn.microsoft.com/cloud_solution_architect/2015/05/05/creating-azure-vms-with-arm-powershell-cmdlets/ 15.https://msdn.microsoft.com/en-us/powershell/gallery/psget/script/psget_new-scriptfileinfo 16.https://msdn.microsoft.com/en-us/powershell/gallery/psget/script/psget_publish-script KEYWORDS: Mnemonic; [R]esilient<[R]esource Group> [S]ervers<[S]torage Account> [N]eed<Virtual [N]etwork> [V]irtual Machines<[VMs] with [N]etworks<[N]etwork Security Groups> and [A]vailability Sets<[A]vailability Sets> LICENSE: The MIT License (MIT) Copyright (c) 2016 Preston K. Parsard Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. DISCLAIMER: THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code. CONSENT: By using this script, you hereby acknowledge that you have the consent of any entity, i.e. organization, agency or individual whose data will be processed by this script, such as Azure IaaS or PaaS resources properties and metadata. PRIVACY: The data collected by this script from prompts, logs and transcripts, will be stored, accessed, and used by the entity or individual executing it for the purpose of tracking, troubleshooting and auditing deployment of Azure IaaS or PaaS based resources. Since logs and transcripts in this sample script are saved in the $env:USERNAME path, i.e. c:\users\<username>\..., it is soley under discretion of of the logged on user or entity to which the user is a member of to share these logs and transcripts. The image, voice, video or text understanding capabilities of 0004-New-AzureRmVmAvailabilitySetPsg.ps1 uses Microsoft Cognitive Services. Microsoft will receive the images, audio, video, and other data that you upload (via this app) for service improvement purposes. To report abuse of the Microsoft Cognitive Services to Microsoft, please visit the Microsoft Cognitive Services website at https://www.microsoft.com/cognitive-services, and use the “Report Abuse” link at the bottom of the page to contact Microsoft. For more information about Microsoft privacy policies please see their privacy statement here: https://go.microsoft.com/fwlink/?LinkId=521839. ABUSE: We caution users of this script to avoid the following intent or actions: 1. Invade anyone's privacy by attempting to discover, harvest, collect, store, or publish private or personally identifiable information without their knowledge and consent; 2. Harm or exploit minors in any way; or 3. Defame, abuse, harass, stalk, threaten, or otherwise violate the legal rights 4. This script is not to be used by children under the age of fourteen. INFO: Microsoft Cognitive Services – Online Services Agreement: https://go.microsoft.com/fwlink/?LinkId=533207 Microsoft Privacy Statement: https://go.microsoft.com/fwlink/?LinkId=521839 ATTRIBUTION: Powered by Microsoft’s Cognitive Services: https://www.microsoft.com/cognitive-services **************************************************************************************************************************************************************************** #> <# WORK ITEMS TASK: #> <# *************************************************************************************************************************************************************************** REVISION/CHANGE RECORD -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- DATE VERSION NAME CHANGE -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 26 APR 2016 0.0.0.1 Preston K. Parsard Initial release. 30 JUN 2016 0.0.0.2 Preston K. Parsard Updated script file name, plus minor edits; removed non-functional comments. 11 JUL 2016 0.0.0.3 Preston K. Parsard Added code to select subscription based on matching subscription ID of subscription name provided in response to prompt. 11 JUL 2016 0.0.0.4 Preston K. Parsard Updated script to use external PSGallery module for logging functions and enhanced logging activity. 11 JUL 2016 1.0.0.0 Preston K. Parsard Applying the verbose common parameter where possible for more details. 14 JUL 2016 1.0.0.1 Preston K. Parsard Added reference for WriteToLogs module (https://www.powershellgallery.com/packages/WriteToLogs/1.0.19). 14 JUL 2016 1.0.0.2 Preston K. Parsard Applied PSScriptInfo to header for publishing to PSGallery. 17 OCT 2016 1.1.0.0 Preston K. Parsard Updated description and changed versioning format from ##.##.#### to #.#.#.# to match the standard module versioning convention format. 18 OCT 2016 1.1.0.1 Preston K. Parsard Created log file earlier in script, added the Start-Transcript feature that records a transcript in a separate file, and created a new logging directory name of "New-AzureRmAvSet". 18 OCT 2016 1.1.0.2 Preston K. Parsard Prompt user for location to offer more flexibility instead of hard-coding in script 18 OCT 2016 1.1.0.3 Preston K. Parsard Prompts user to open both $Log and $Transcript files upon completion of script. 19 OCT 2016 1.1.0.4 Preston K. Parsard Updated the image name to reflect the new 2016-Datacenter image. 20 OCT 2016 1.1.0.5 Preston K. Parsard Fixed regular expression for resource group naming standard to comply with the format rg##, where ## represents a two digit numeric sequence. 20 OCT 2016 1.1.0.6 Preston K. Parsard Simplified regular expression to restrict attendee number input to 4 digits. 20 OCT 2016 1.1.0.7 Preston K. Parsard Simplified regular expression to restrict site code input to 3 letters. 20 OCT 2016 1.1.0.8 Preston K. Parsard Removed exit statement at end so that the ISE/PowerShell console will still be available after the script executes. 20 OCT 2016 1.1.0.9 Preston K. Parsard Added #Requires tags for Version 5.0 and RunAsAdministrator, since the Start/Stop-Transcript cmdlets are used and administrative permissions are required for the package installer 20 OCT 2016 1.1.0.10 Preston K. Parsard Upgraded VM image size from Standard_A1 to Standard_D1_v2. #> # Resets profiles in case you have multiple Azure Subscriptions and connects to your Azure Account [Uncomment if you haven't already authenticated to your Azure subscription] Clear-AzureProfile -Force Login-AzureRmAccount # Construct custom path for log files $LogDir = "New-AzureRmAvSet" $LogPath = $env:HOMEPATH + "\" + $LogDir If (!(Test-Path $LogPath)) { New-Item -Path $LogPath -ItemType Directory } #End If # Create log file with a "u" formatted time-date stamp $StartTime = (((get-date -format u).Substring(0,16)).Replace(" ", "-")).Replace(":","") $24hrTime = $StartTime.Substring(11,4) $LogFile = "New-AzureRmAvSet-LOG" + "-" + $StartTime + ".log" $TranscriptFile = "New-AzureRmAvSet-TRANSCRIPT" + "-" + $StartTime + ".log" $Log = Join-Path -Path $LogPath -ChildPath $LogFile $Transcript = Join-Path $LogPath -ChildPath $TranscriptFile # Create Log file New-Item -Path $Log -ItemType File -Verbose # Create Transcript file New-Item -Path $Transcript -ItemType File -Verbose Start-Transcript -Path $Transcript -IncludeInvocationHeader -Append -Verbose # To avoid multiple versions installed on the same system, first uninstall any previously installed and loaded versions if they exist Uninstall-Module -Name WriteToLogs -AllVersions -ErrorAction SilentlyContinue -Verbose # If the WriteToLogs module isn't already loaded, install and import it for use later in the script for logging operations If (!(Get-Module -Name WriteToLogs)) { # https://www.powershellgallery.com/packages/WriteToLogs Set-PSRepository -Name PSGallery -InstallationPolicy Trusted Install-PackageProvider -Name Nuget -ForceBootstrap -Force Install-Module -Name WriteToLogs -Repository PSGallery -Force -Verbose Import-Module -Name WriteToLogs -Verbose } #end If #region INITIALIZE VALUES $BeginTimer = Get-Date -Verbose Do { # Subscription name (Get-AzureRmSubscription).SubscriptionName [string] $Subscription = Read-Host "Please enter your subscription name, i.e. [MSDN | MSFT] " $Subscription = $Subscription.ToUpper() } #end Do Until (($Subscription) -ne $null) # Selects subscription based on subscription name provided in response to the prompt above Select-AzureRmSubscription -SubscriptionId (Get-AzureRmSubscription -SubscriptionName $Subscription).SubscriptionId Do { # Resource Group name [string] $rg = Read-Host "Please enter a new resource group name [rg##] " } #end Do Until (($rg) -match '^rg\d{2}$') Do { # This is a uniquely assigned number for each course attendee so that the domain and Azure resources will also have unique names within the same course # For class-wide demo scripts, this number will be the last 4 digits of the request number [string]$AttendeeNum = Read-Host "Please enter your 4 digit attendee or request number, i.e. [0000] " } Until ($AttendeeNum -match '^[0-9][0-9][0-9][0-9]$') Do { # The site code refers to a 3 letter airport code of the nearest major airport to the training site [string]$SiteCode = Read-Host "Please enter your 3 character site code, i.e. [ATL] " $SiteCode = $SiteCode.ToUpper() } #end Do Until ($SiteCode -match '^[A-Z]{3}$') Do { # The site code refers to a 3 letter airport code of the nearest major airport to the training site [int]$InstanceCount = Read-Host "Please enter the total number of DC instances required [1-4] " } #end Do Until ($InstanceCount -le 4 -AND $InstanceCount -ne $null) # Create and populate prompts object with property-value pairs # PROMPTS (PromptsObj) $PromptsObj = [PSCustomObject]@{ pVerifySummary = "Is this information correct? [YES/NO]" pAskToOpenLogs = "Would you like to open the deployment log now ? [YES/NO]" } #end $PromptsObj # Create and populate responses object with property-value pairs # RESPONSES (ResponsesObj): Initialize all response variables with null value $ResponsesObj = [PSCustomObject]@{ pProceed = $null pOpenLogsNow = $null } #end $ResponsesObj Do { # The location refers to a geographic region of an Azure data center $Regions = Get-AzureRmLocation | Select-Object -ExpandProperty Location Write-ToConsoleAndLog -Output "The list of available regions are :" -Log $Log Write-ToConsoleAndLog -Output "" -Log $Log Write-ToConsoleAndLog -Output $Regions -Log $Log Write-ToConsoleAndLog -Output "" -Log $Log $EnterRegionMessage = "Please enter the geographic location (Azure Data Center Region) to which you would like to deploy these resources, i.e. [eastus2 | westus2]" Write-ToLogOnly -Output $EnterRegionMessage -Log $Log [string]$Region = Read-Host $EnterRegionMessage $Region = $Region.ToUpper() Write-ToConsoleAndLog -Output "`$Region selected: $Region " -Log $Log Write-ToConsoleAndLog -Output "" -Log $Log } #end Do Until ($Region -in $Regions) New-AzureRmResourceGroup -Name $rg -Location $Region -Verbose # Storage account name prefix $SaPrefix = "sto" # Generate storage account name based on prefix and attendee number $StorageAcctName = $SaPrefix + $AttendeeNum # VM image details $Publisher = "MicrosoftWindowsServer" $offer = "WindowsServer" [string]$sku = "2016-Datacenter" $ImageName2016 = Get-AzureRmVMImage –Location $Region –Offer $offer –PublisherName $publisher –SKUs $sku $Version = "latest" # User name is specified directly in script $UniversalAdmName = "entadmin" # Virtual Machine size $VmSize = "Standard_D1_v2" # Availability set $AvSetDcName = "AvSetDC" # NTDS volume drive $NtdsDiskName = "NTDS" # SYSVOL volume drive $SysvDiskName = "SYSV" # This is the generic top-level domain that will be used in the FQDN of a new domain that can be created later if desired $gtld = ".lab" $SiteNamePrefix = "net" $cred = Get-Credential -UserName $UniversalAdmName -Message "Enter password for user: $UniversalAdmName" # $UniversalPW = $cred.GetNetworkCredential().password $DelimDouble = ("=" * 100 ) $Header = "AZURE RM DC DEPLOYMENT DEMO: " + $StartTime # Create and populate site, subnet and VM properties of the domain with property-value pairs $ObjDomain = [PSCustomObject]@{ pFQDN = "R" + $AttendeeNum + $gtld pDomainName = "R" + $AttendeeNum pSite = $SiteNamePrefix + $AttendeeNum # Subnet names matches the VM roles (DC = Domain Controller, AP = Application servers or member servers) pSubNetDC = "DC" pSubNetAP = "AP" pDC = $SiteCode + "DC" # Based on the latest image of Windows Server 2016 } #end $ObjDomain # Subnet for domain controllers $DcSubnet = New-AzureRmVirtualNetworkSubnetConfig -Name $ObjDomain.pSubnetDC -AddressPrefix 10.0.0.0/28 -Verbose # Subnet for member servers (AP = Application servers) $ApSubnet = New-AzureRmVirtualNetworkSubnetConfig -Name $ObjDomain.pSubnetAP -AddressPrefix 10.0.0.16/28 -Verbose $Vnet = New-AzureRmVirtualNetwork -Name $ObjDomain.pSite -ResourceGroupName $rg -Location $Region -AddressPrefix 10.0.0.0/26 -Subnet $DcSubnet,$ApSubnet -Verbose # NSG Configuration # https://www.petri.com/create-azure-network-security-group-using-arm-powershell # Create the NSG names using 'NSG-' as a prefix $NsgDcSubnetName = "NSG-$($ObjDomain.pSubnetDC)" $NsgApSubnetName = "NSG-$($ObjDomain.pSubnetAP)" # Create the AllowRdpInbound rules $NsgRuleAllowRdpIn = New-AzureRmNetworkSecurityRuleConfig -Name "AllowRdpInbound" -Direction Inbound -Priority 100 -Access Allow -SourceAddressPrefix "Internet" -SourcePortRange "*" ` -DestinationAddressPrefix "VirtualNetwork" -DestinationPortRange 3389 -Protocol Tcp -Verbose $NsgDcSubnetObj = New-AzureRmNetworkSecurityGroup -Name $NsgDcSubnetName -ResourceGroupName $rg -Location $Region -SecurityRules $NsgRuleAllowRdpIn -Verbose $NsgApSubnetObj = New-AzureRmNetworkSecurityGroup -Name $NsgApSubnetName -ResourceGroupName $rg -Location $Region -SecurityRules $NsgRuleAllowRdpIn -Verbose # Associate NSGs with VNETs Set-AzureRmVirtualNetworkSubnetConfig -VirtualNetwork $Vnet -Name $ObjDomain.pSubnetDC -AddressPrefix $DcSubnet.AddressPrefix -NetworkSecurityGroup $NsgDcSubnetObj | Set-AzureRmVirtualNetwork -Verbose Set-AzureRmVirtualNetworkSubnetConfig -VirtualNetwork $Vnet -Name $ObjDomain.pSubnetAP -AddressPrefix $ApSubnet.AddressPrefix -NetworkSecurityGroup $NsgApSubnetObj | Set-AzureRmVirtualNetwork -Verbose # Specify disk size as 10 GiB [int]$DataDiskSize = 10 # Create the avialability set for the [future] DCs $DcAvSet = New-AzureRmAvailabilitySet -ResourceGroupName $rg -Name $AvSetDcName -Location $Region -Verbose # Populate Summary Display Object # Add properties and values # Make all values upper-case $SummObj = [PSCustomObject]@{ SUBSCRIPTION = $Subscription.ToUpper() RESOURCEGROUP = $rg SITECODE = $SiteCode.ToUpper() ATTENDEENUM = $AttendeeNum.ToUpper() DOMAINFQDN = $ObjDomain.pFQDN.ToUpper() DOMAINNETBIOS = $ObjDomain.pDomainName.ToUpper() SITENAME = $ObjDomain.pSite.ToUpper() DCSUBNET = $ObjDomain.pSubNetDC.ToUpper() NSGDC = $NsgDcSubnetName.ToUpper() APSUBNET = $ObjDomain.pSubNetAP.ToUpper() NSGAP = $NsgApSubnetName.ToUpper() DCPREFIX = $ObjDomain.pDC.ToUpper() # This is the number of VMs and associated VM resources that will be created INSTANCES = $InstanceCount STORAGEACCT = $StorageAcctName.ToUpper() REGION = $Region.ToUpper() LOGPATH = $Log } #end $SummObj #endregion INITIALIZE VALUES #region FUNCTIONS Function New-RandomString { $CombinedCharArray = @() $ComplexityRuleSets = @() $PasswordArray = @() # PCR here means [P]assword [C]omplexity [R]equirement, so the $PCRSampleCount value represents the number of characters that will be generated for each password complexity requirement (alpha upper, lower, and numeric) $PCRSampleCount = 4 $PCR1AlphaUpper = ([char[]]([char]65..[char]90)) $PCR3AlphaLower = ([char[]]([char]97..[char]122)) $PCR4Numeric = ([char[]]([char]48..[char]57)) # Add all of the PCR... arrays into a single consolidated array $CombinedCharArray = $PCR1AlphaUpper + $PCR3AlphaLower + $PCR4Numeric # This is the set of complexity rules, so it's an array of arrays $ComplexityRuleSets = ($PCR1AlphaUpper, $PCR3AlphaLower, $PCR4Numeric) # Sample 4 characters from each of the 3 complexity rule sets to generate a complete 12 character random string ForEach ($ComplexityRuleSet in $ComplexityRuleSets) { Get-Random -InputObject $ComplexityRuleSet -Count $PCRSampleCount | ForEach-Object { $PasswordArray += $_ } } #end ForEach [string]$RandomStringWithSpaces = $PasswordArray [string]$Script:RandomString = $RandomStringWithSpaces.Replace(" ","") } #end Function # Create DC VM Function Add-VM { # If the number of servers will be less than 9, pad with 0, so that the 3rd server would have a pulbic ip of dcvip03 instead of dcvip3 or a nic of dcnic03 as opposed to dcnic3. # This keeps the alignment consistent where all resources will have the same name lengths Write-WithTime -Output "Padding public IP and NIC resource names if necessary..." -Log $Log Switch ($i) { { $i -le 9 } { $DcVipPrefix = "dcvip0" $DcNicPrefix = "dcnic0" } #end condition default { $DcVipPrefix = "dcvip" $DcNicPrefix = "dcnic" } #end default } #end Switch # Create the public ip (VIP) and NIC names based on the prefix and index Write-WithTime -Output "Creating public IP name..." -Log $Log $DcVipName = $DcVipPrefix + $i Write-WithTime -Output "Creating NIC name..." -Log $Log $DcNicName = $DcNicPrefix + $i # Construct the drive names for the SYSTEM, NTDS and SYSVOL drives Write-WithTime -Output "Constructing SYSTEM drive name page blob..." -Log $Log $DCSYSTvhdUri = $sa.PrimaryEndpoints.Blob.ToString() + "vhds/" + "$($ObjDomain.pDC)-SYST.vhd" Write-WithTime -Output "Constructing NTDS drive name page blob..." -Log $Log $DCNTDSvhdUri = $sa.PrimaryEndpoints.Blob.ToString() + "vhds/" + "$($ObjDomain.pDC)-NTDS.vhd" Write-WithTime -Output "Constructing SYSVOL drive name page blob..." -Log $Log $DCSYSVvhdUri = $sa.PrimaryEndpoints.Blob.ToString() + "vhds/" + "$($ObjDomain.pDC)-SYSV.vhd" # $x represents the value of the last octect of the private IP address. We skip the first 3 addresses in the network address because they are always reserved in Azure $x = $i + 3 # NOTE: Domain labels have to be lower case Write-WithTime -Output "Creating DNS domain label..." -Log $Log # Add a random infix (4 numeric digits) inside the Dnslabel name to avoid conflicts with existing deployments generated from this script. The -pip suffix indicates this is a public IP New-RandomString $DnsLabelInfix = $RandomString.SubString(8,4) $DomainLabel = $objDomain.pDC.ToLower() + $DnsLabelInfix + "-pip" Write-WithTime -Output "Creating public IP..." -Log $Log # Now we can string all the pre-requisites together to construct both the VIP and NIC $DCvip = New-AzureRmPublicIpAddress -ResourceGroupName $rg -Name $DcVipName -Location $Region -AllocationMethod Static -DomainNameLabel $DomainLabel -Verbose Write-WithTime -Output "Creating NIC..." -Log $Log $DCnic = New-AzureRmNetworkInterface -ResourceGroupName $rg -Name $DcNicName -Location $Region -PrivateIpAddress "10.0.0.$x" -SubnetId $Vnet.Subnets[0].Id -PublicIpAddressId $DCvip.Id -Verbose # If the VM doesn't aready exist, configure and create it If (!((Get-AzureRmVM -ResourceGroupName $rg).Name -match $ObjDomain.pDC)) { Write-WithTime -Output "VM $($ObjDomain.pDC) doesn't already exist. Configuring..." -Log $Log # Setup new vm configuration $DcvmConfig = New-AzureRmVMConfig –VMName $ObjDomain.pDC -VMSize $vmSize -AvailabilitySetId $DcAvSet.Id | Set-AzureRmVMOperatingSystem -Windows -ComputerName $ObjDomain.pDC -Credential $cred -ProvisionVMAgent -EnableAutoUpdate | Set-AzureRmVMSourceImage -PublisherName $publisher -Offer $offer -Skus $sku -Version $version | Set-AzureRmVMOSDisk -Name $ObjDomain.pDC -VhdUri $DCSYSTvhdUri -Caching ReadWrite -CreateOption fromImage | Add-AzureRmVMNetworkInterface -Id $DCnic.Id -Verbose # Create new VM Write-WithTime -Output "Creating VM from configuration..." -Log $Log New-AzureRmVM -ResourceGroupName $rg -Location $Region -VM $DcvmConfig -Verbose # Add NIC Write-WithTime -Output "Adding NIC..." -Log $Log Set-AzureRmNetworkInterface -NetworkInterface $DCnic -Verbose # Add data disks Write-WithTime -Output "Adding data disks..." -Log $Log $vmdc = Get-AzureRmVM -ResourceGroupName $rg -Name $ObjDomain.pDC Write-WithTime -Output "Adding NTDS disk..." -Log $Log Add-AzureRmVMDataDisk -VM $vmdc -Name $NtdsDiskName -VhdUri $DCNTDSvhdUri -LUN 0 -Caching None -DiskSizeinGB $DataDiskSize -CreateOption Empty -Verbose Write-WithTime -Output "Adding SYSVOL disk..." -Log $Log Add-AzureRmVMDataDisk -VM $vmdc -Name $SysvDiskName -VhdUri $DCSYSVvhdUri -LUN 1 -Caching None -DiskSizeinGB $DataDiskSize -CreateOption Empty -Verbose # Update disk configuration Write-WithTime -Output "Applying new disk configurations..." -Log $Log Update-AzureRmVM -ResourceGroupName $rg -VM $vmdc -Verbose } #end If else { Write-ToConsoleAndLog -Output "$($ObjDomain.pDC) already exists..." -Log $Log } #end else } #End function #endregion FUNCTIONS #region MAIN # Clear screen # Clear-Host # Display header Write-ToConsoleAndLog -Output $DelimDouble -Log $Log Write-ToConsoleAndLog -Output $Header -Log $Log Write-ToConsoleAndLog -Output $DelimDouble -Log $Log # Display Summary Write-ToConsoleAndLog -Output $SummObj -Log $Log Write-ToConsoleAndLog -Output $DelimDouble -Log $Log # Verify parameter values Do { $ResponsesObj.pProceed = read-host $PromptsObj.pVerifySummary $ResponsesObj.pProceed = $ResponsesObj.pProceed.ToUpper() } Until ($ResponsesObj.pProceed -eq "Y" -OR $ResponsesObj.pProceed -eq "YES" -OR $ResponsesObj.pProceed -eq "N" -OR $ResponsesObj.pProceed -eq "NO") # Record prompt and response in log Write-ToLogOnly -Output $PromptsObj.pVerifySummary -Log $Log Write-ToLogOnly -Output $ResponsesObj.pProceed -Log $Log # Exit if user does not want to continue if ($ResponsesObj.pProceed -eq "N" -OR $ResponsesObj.pProceed -eq "NO") { Write-ToConsoleAndLog -Output "Deployment terminated by user..." -Log $Log PAUSE EXIT } #end if ne Y else { # Proceed with deployment Write-ToConsoleAndLog -Output "Deploying environment..." -Log $Log # Storage Write-WithTime -Output "Creating storage account $StorageAcctName ..." -Log $Log # The following error will be displayed, due to the ARM PowerShell module missing the Test-Azure command. See: Symptom: https://github.com/Azure/azure-powershell/issues/639 # Test-AzureName : No default subscription has been designated. Use Select-AzureSubscription -Default <subscriptionName> to set the default subscription... If (!(Get-AzureRmStorageAccount -ResourceGroupName $rg -Name $StorageAcctName -ErrorAction SilentlyContinue)) { Write-WithTime -Output "Storage account $StorageAcctName does not already exist. Creating..." -Log $Log New-AzureRmStorageAccount -ResourceGroupName $rg -Name $StorageAcctName -Location $Region -Type Standard_LRS -Verbose } #end If else { Write-WithTime -Output "Storage Account: $StorageAcctName already exist. Skipping..." -Log $Log } #end else $sa = Get-AzureRmStorageAccount -ResourceGroupName $rg -Name $StorageAcctName # Create DC VM(s). Note that we pad the VM name here again, as we did for the VIPs and NICs above to ensure a consistent name length for VM resources Write-WithTime -Output "Padding name of VM for a consistent length if necessary..." -Log $Log For ($i = 1;$i -le $InstanceCount;$i++) { Switch ($i) { { $i -le 9 } { $ObjDomain.pDC = $SiteCode + "DC0" + $i } default { # The VM name is constructed from the site code, "DC" role prefix and the numeric index $i $ObjDomain.pDC = $SiteCode + "DC" + $i } #end default } #end switch Write-WithTime -Output "Building $($ObjDomain.pDC)..." -Log $Log Add-VM } #end For ($i...) } #end else #endregion MAIN #region FOOTER # Calculate elapsed time Write-WithTime -Output "Calculating script execution time..." -Log $Log Write-WithTime -Output "Getting current date/time..." -Log $Log $StopTimer = Get-Date Write-WithTime -Output "Formating date/time to replace commas(,) with dashes(-)..." -Log $Log $EndTime = (((Get-Date -format u).Substring(0,16)).Replace(" ", "-")).Replace(":","") Write-WithTime -Output "Calculating elapsed time..." -Log $Log $ExecutionTime = New-TimeSpan -Start $BeginTimer -End $StopTimer $Footer = "SCRIPT COMPLETED AT: " Write-ToConsoleAndLog -Output $DelimDouble -Log $Log Write-ToConsoleAndLog -Output "$Footer + $EndTime" -Log $Log Write-ToConsoleAndLog -Output "TOTAL SCRIPT EXECUTION TIME: $ExecutionTime" -Log $Log Write-ToConsoleAndLog -Output $DelimDouble -Log $Log # Prompt to open logs Do { $ResponsesObj.pOpenLogsNow = read-host $PromptsObj.pAskToOpenLogs $ResponsesObj.pOpenLogsNow = $ResponsesObj.pOpenLogsNow.ToUpper() } Until ($ResponsesObj.pOpenLogsNow -eq "Y" -OR $ResponsesObj.pOpenLogsNow -eq "YES" -OR $ResponsesObj.pOpenLogsNow -eq "N" -OR $ResponsesObj.pOpenLogsNow -eq "NO") # Exit if user does not want to continue if ($ResponsesObj.pOpenLogsNow -eq "Y" -OR $ResponsesObj.pOpenLogsNow -eq "YES") { Start-Process notepad.exe $Log Start-Process notepad.exe $Transcript } #end if # End of script Write-WithTime -Output "END OF SCRIPT!" -Log $Log # Close transcript file Stop-Transcript -Verbose <# # Decommission deployed environment by removing resource group NOTE: [TEST-DEV / POC SCENARIOS ONLY!!!] To quickly and conveniently remove all the resources that this script generated in order to re-run the script you can uncomment and execute the expression below: #> # Get-AzureRmResourceGroup | Where-Object { $_.ResourceGroupName -match $rg } | Remove-AzureRmResourceGroup -Force #endregion FOOTER Pause |