Skip to main content

PowerShell + Azure : Deploy a DC

Recently my laptop got stolen and that gave me a push to build my lab on Azure. I tweeted this and got an awesome reply by Jim Christopher[PowerShell MVP] :



Thanks to my friend Fenil Shah who lend me his laptop to try out Azure.
Cheers to having awesome friends :)


I thought it would be better if I put my notes as a post. These are entirely for my reference ;) 

The best posts around Azure + PowerShell are by Michael Washam which can be found on his blog here.

My Action plan is to configure a ServerCore Server 2012 R2 machine running Active Directory for this post from scratch, I don't have anything right now on my azure account.

Below are the steps:


  1. Sign Up for Azure (Free Trial is available)
  2. Install Azure PowerShell Module & Configure your subscription
  3. Create a Virtual Net for your LAB
  4. Deploy the VM
  5. Connecting to VM using PSRemoting
  6. Add a new Data Disk to VM
  7. Install ADDS and a new domain.
Steps 1-3 are one time activity, next time you want to spin a VM then no need to do these.

Sign Up for Azure

Go to https://azure.microsoft.com/en-us/ to sign up for a free trial of azure.
One has to supply their Credit Card / Debit Card information for verification which will deduct $1 (this is refunded back..don't worry you misers :D ).


Note - There is a credit limit of $200 in free trail and by default your subscription won't go above this limit, so be assured. 


Install Azure PowerShell Module & Configure your Subscription

There are very good articles below which describes this step:



Following the above two articles below is what I did:

001
002
003
004
005
006
007
008
009
# Get the Settings file
Get-AzurePublishSettingsFile

#Import the file
Import-AzurePublishSettingsFile -PublishSettingsFile "C:\Temp\Visual Studio Ultimate with MSDN-7-19-2014-credentials.publishsettings"

#Remove the Settings once imported
Remove-item "C:\Temp\Visual Studio Ultimate with MSDN-7-19-2014-credentials.publishsettings"



Once you have the settings file imported, you can remove it and then you can see that the Subscription information has been imported successfully using


001
002
003

#get the Subscription details
Get-AzureSubscription

After one has imported the Subscription information , one has to select the Subscription in order to use it , below is what I did [Note - I have only one subscription so I used (Get-AzureSubscription).SubscriptionName below ] .


001
Select-AzureSubscription -SubscriptionName (Get-AzureSubscription).SubscriptionName

Now to verify that this is the Subscription my Azure cmdlets will run against run the below and it should show your default Subscription details:


001
Get-AzureSubscription -Default

At this point we need to have a storage account before proceeding further as this is where your data (VM's VHD etc ) will be stored. I am going to create a storage account with the name "dexterposhstorage" (note all lowercase letters and numbers allowed).


001
002
003
004
005
006
#create the Storage Account
New-AzureStorageAccount -Location "Southeast Asia" -StorageAccountName "dexterposhstorage" -Label "DexLAB" -Description "Storage Account for my LABs" -Verbose

#Turn off the Geo Replication...am just using it for my lab
Set-AzureStorageAccount -StorageAccountName dexterposhstorage -GeoReplicationEnabled $false -Verbose

While doing this if you get an error like below:

New-AzureStorageAccount : Specified argument was out of the range of valid values.

Then probably the name you choose for the storage account is already taken or it doesn't adhere to the naming standards (only lowercase letters and numbers).

Once you have the storage account created, set it as the current storage account to be used for your default subscription (as you can create many storage account but only use one at a time )


001
002
003
#set your storage account
Set-AzureSubscription -SubscriptionName (Get-AzureSubscription -Default).SubscriptionName -CurrentStorageAccountName "deterposhstorage"

Note - The above steps are one time activity. Once you have followed the above steps then next time you have to just load the Azure PS Module and start automation.


3. Create a Virtual Net for your LAB


In order to run a full blown LAB in Azure with my own DNS, AD etc.  I have to use Virtual Networks. Right now the easiest way to do this is using the portal as there are no cmdlet to create a new VNET, there is a Set-AzureVNetConfig which requires us to create and manipulate an XML file to create VNETs, but I was looking to do this ASAP ( there are links in resources section if you want to automate this part too).

Below is the XML which I got after adding the VNET from the portal




Below is how the VNet looks like in the Azure Management Portal:




Note that in the subnet "AD" the first usable IP address is 192.168.0.4

If you want to do this using PowerShell too (which I will eventually then refer the resources at the end).


4. Deploy VM

If you are deploying a VM for first time then you have to create an affinity group (optional) , cloud service & Storage account (mandatory).

Now let's define few PowerShell variables for Affinity Group, Cloud Service, Storage Account , DNS Server IP Address and Name of our Domain Controller.

001
002
003
004
005
006

$AffinityGroup = "DexAffinityGroup"
$cloudService = "DexCloudService"
$StorageAccount = "dexterposhstorage"
$DNSIP = '192.168.0.4' #the first usable IP address in our Subnet "AD"
$VMName = 'DexDC' #Name of the VM running our Domain Controller

Now time to create a new Affinity Group.
Also I have turned off Geo-replication as this is my test LAB (my preference).

001
002
003
004
005
006
007
008
009
010
#create a new Affinity Group for my Lab resources
New-AzureAffinityGroup -Name $AffinityGroup -Location "Southeast Asia" -Label DexLAB -Description "Affinity Group for my LAB" -Verbose


In Azure when you deploy a VM it is associated with a cloud service (which is a logical container for Azure resources). So let's create a new one 


001
002
003
#Now create a new Cloud Service
New-AzureService -ServiceName $cloudService -AffinityGroup $AffinityGroup -Label DexLAB -Description "Cloud Service for my LAB" -Verbose

The house keeping activities needed to deploy VMs is done for my Azure Subsccription. Now I need to select a Image from the gallery and use it to deploy my VMs. The cmdlet to get the images is Get-AzureImage but out of all the images am looking only for the latest Server 2012 R2 image.

I use the below to get the image stored in the variable $image (see the use of -OutVariable)



[ADMIN] PS C:\> Get-AzureVMImage | where { $_.ImageFamily -eq “Windows Server 2012 R2 Datacenter” } | Sort-Object -Descending -
Property PublishedDate | Select-Object -First 1 -OutVariable image


ImageName            : a699494373c04fc0bc8f2bb1389d6106__Windows-Server-2012-R2-201407.01-en.us-127GB.vhd
OS                   : Windows
MediaLink            :
LogicalSizeInGB      : 128
AffinityGroup        :
Category             : Public
Location             : East Asia;Southeast Asia;North Europe;West Europe;Japan West;Central US;East US;East US 2;South
                       Central US;West US
Label                : Windows Server 2012 R2 Datacenter, July 2014
Description          : At the heart of the Microsoft Cloud OS vision, Windows Server 2012 R2 brings Microsoft's experience
                       delivering global-scale cloud services into your infrastructure. It offers enterprise-class
                       performance, flexibility for your applications and excellent economics for your datacenter and hybrid
                       cloud environment. This image includes Windows Server 2012 R2 Update.
Eula                 :
ImageFamily          : Windows Server 2012 R2 Datacenter
PublishedDate        : 7/21/2014 12:30:00 PM
IsPremium            : False
IconUri              : WindowsServer2012R2_45.png
SmallIconUri         : WindowsServer2012R2_45.png
PrivacyUri           :
RecommendedVMSize    :
PublisherName        : Microsoft Windows Server Group
OperationDescription : Get-AzureVMImage
OperationId          : b556cf7a-a4e8-c744-8471-f0ea0e3473ca
OperationStatus      : Succeeded



[ADMIN] PS C:\> $image.imagename
a699494373c04fc0bc8f2bb1389d6106__Windows-Server-2012-R2-201407.01-en.us-127GB.vhd


While deploying VMs in Azure one has to build configurations before finally creating it, so let's build the first one to specify the VM Instance size, image name (from above) etc and store the config in a variable named $NewVM.

Note the use of Tee-Object to store the Object in Variable. Now people might wonder why not use the -OutVariable as above then a small hint , go ahead and use it and check the type of the object being returned ;)



[ADMIN] PS C:\>  New-AzureVMConfig -Name $VMName -InstanceSize Small -ImageName $image.ImageName -DiskLabel "OS" -HostCaching R
eadOnly | Tee-Object -Variable NewVM


AvailabilitySetName               :
ConfigurationSets                 : {}
DataVirtualHardDisks              : {}
Label                             : DexDC
OSVirtualHardDisk                 : Microsoft.WindowsAzure.Commands.ServiceManagement.Model.PersistentVMModel.OSVirtualHardDis
                                    k
RoleName                          : DexDC
RoleSize                          : Small
RoleType                          : PersistentVMRole
WinRMCertificate                  :
X509Certificates                  :
NoExportPrivateKey                : False
NoRDPEndpoint                     : False
NoSSHEndpoint                     : False
DefaultWinRmCertificateThumbprint :
ProvisionGuestAgent               : True
ResourceExtensionReferences       :
DataVirtualHardDisksToBeDeleted   :

Time to add another config to our VM which will specify the Admin User Name and Password for the VM:
001
002
$password = "P@ssw0rd321"
$username = "DexterPOSH"


[ADMIN] PS C:\> Add-AzureProvisioningConfig -Windows -Password $password -AdminUsername $username -DisableAutomaticUpdates -VM
$newVM


AvailabilitySetName               :
ConfigurationSets                 : {DexDC, Microsoft.WindowsAzure.Commands.ServiceManagement.Model.PersistentVMModel.NetworkC
                                    onfigurationSet}
DataVirtualHardDisks              : {}
Label                             : DexDC
OSVirtualHardDisk                 : Microsoft.WindowsAzure.Commands.ServiceManagement.Model.PersistentVMModel.OSVirtualHardDis
                                    k
RoleName                          : DexDC
RoleSize                          : Small
RoleType                          : PersistentVMRole
WinRMCertificate                  :
X509Certificates                  : {}
NoExportPrivateKey                : False
NoRDPEndpoint                     : False
NoSSHEndpoint                     : False
DefaultWinRmCertificateThumbprint :
ProvisionGuestAgent               : True
ResourceExtensionReferences       : {BGInfo}
DataVirtualHardDisksToBeDeleted   :

The first VM deployed in our LAB will be a Domain Controller and we need to make sure that it gets the same local IP Address, that's why we created a Subnet named "AD" in our Virtual Network and we will place our VM there (only machine in that subnet, ensuring that it gets the first usable IPaddress there).
In addition to this as an extra precaution , we can use the cmdlet Set-AzureStaticVNetIP to bind the IP address to our VM.


001
002
003
004
005
# set the AD Subnet for this machine
 Set-AzureSubnet -SubnetNames AD -VM $newVM

 #set the Static VNET IPAddress of 192.168.0.4 for our VM
 Set-AzureStaticVNetIP -IPAddress $DNSIP -VM $newVM


After all the configurations being created, we will finally create the new VM


001
New-AzureVM -ServiceName $cloudService -VMs $newVM -VNetName "DexVNET"  -AffinityGroup DexAffinityGroup

As an alternative one can use the New-AzureQuickVM (use this if you are using Azure Automation feature). There are few cases where New-AzureVM fails miserably.

Note - In addition one can specify the -WaitForBoot (New-AzureVM) to pause the Script execution until the VM is up and ready.



Connecting to Azure VM using PSRemoting


Once the VM is up and running it is time to add a new disk to it for storing the SysVol folder for the AD Domain Services. I wanted to do this using PowerShell too as the Server 2012 supports disk management tasks. But for this I need to configure my laptop to be able to talk to the WinRM endpoint sitting behind the cloud service (by default RDP and WinRM endpoints for each of the VMs are opened).

Again this has already been explained at the below link:

http://michaelwasham.com/windows-azure-powershell-reference-guide/introduction-remote-powershell-with-windows-azure/


Following the above link, below code does the work for me:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
$WinRMCert = (Get-AzureVM -ServiceName $CloudService -Name $VMName | select -ExpandProperty vm).DefaultWinRMCertificateThumbprint
$AzureX509cert = Get-AzureCertificate -ServiceName $CloudService -Thumbprint $WinRMCert -ThumbprintAlgorithm sha1

$certTempFile = [IO.Path]::GetTempFileName()
$AzureX509cert.Data | Out-File $certTempFile

# Target The Cert That Needs To Be Imported
$CertToImport = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $certTempFile

$store = New-Object System.Security.Cryptography.X509Certificates.X509Store "Root", "LocalMachine"
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$store.Add($CertToImport)
$store.Close()

Remove-Item $certTempFile

After this I can remote in to my VM running up on Azure and perform all the tasks I want to, isn't it amazing ;)

Using the cmdlet Get-AzureWinRMUri, we get the connection URI.




001
002
003
#Now I can use the Get-AzureWinrmUri
    $WinRMURi = (Get-AzureWinRMUri -ServiceName $cloudService -Name $VMName).AbsoluteUri


also create credential object to be passed on when opening a PSSession.


001
002
003
004
005
006
007
008
#Convert our plain text password to secure string
$passwordsec = ConvertTo-SecureString -String $password -AsPlainText -Force
#create the Creds Object
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username,$passwordsec

#Open up a new PSSession to the Azure VM
$Session = New-PSSession -ConnectionUri $WinRMURi -Credential $cred

Hopefully if we did everything right we will have a PSSession open.


Add a new data disk to VM

Let's add the data disk now.

001
002
003
#add new data disk to store the NTDS and SysVol folders
Add-AzureDataDisk -CreateNew -DiskSizeInGB 20 -DiskLabel "NTDS" -LUN 0 -VM $DexDC  | Update-AzureVM

Please note that at the end we need to pipe the output of Add-AzureDataDisk to Update-AzureVM.

Now if you would have connected using RDP and opened the diskmgmt.msc then you could have added the new disk (GUI way).

But we are going to use PowerShell for that as the server we choose is Server 2012 R2 (which has the disk mgmt cmdlets shipped with it).


Below is the code, which will initialize , partition and format our new disk:
001
002
003
004
005
006
007
008
009
010
011

Invoke-Command -Session $session -ScriptBlock {
    Get-Disk |
    where partitionstyle -eq 'raw' |
    Initialize-Disk -PartitionStyle MBR -PassThru |
    New-Partition -AssignDriveLetter -UseMaximumSize |
    Format-Volume -FileSystem NTFS -NewFileSystemLabel "NTDS" -Confirm:$false

                          
}

You can verify the result by running the Get-Disk cmdlet in the remote PSSession.


Install ADDS and a new domain

Perfect now we have everything to promote this Azure VM as the first domain controller for our new forest.

We will put the NDTS & SysVol folder in our new data disk we added.


001
002
003
004
005
006
007
008
009
010
011

Invoke-Command -Session $Session -ArgumentList @($password-ScriptBlock {
        Param ($password)
        # Set AD install paths
        $drive = get-volume | where { $_.FileSystemLabel -eq “NTDS” }
        $NTDSpath = $drive.driveletter + ":\Windows\NTDS"
        $SYSVOLpath = $drive.driveletter + ":\Windows\SYSVOL"
        write-host "Installing the first DC in the domain"
        Install-WindowsFeature –Name AD-Domain-Services -includemanagementtools
        Install-ADDSForest -DatabasePath $NTDSpath -LogPath $NTDSpath -SysvolPath $SYSVOLpath -DomainName "dex.com" -InstallDns -Force -Confirm:$false -SafeModeAdministratorPassword $password
    }

Reboot your VM and you have your test domain up and ready in the cloud (for me it is dex.com).

One more thing once all is done, I switched my Domain Controller to the ServerCore ;)

Below is the snippet which does it for me.




001
002
003
#Convert to Server Core
Invoke-Command -Session $Session -script { Uninstall-WindowsFeature Server-Gui-Mgmt-Infra,Server-Gui-Shell -Restart}



That's it for today, probably one more post will follow which will focus on doing this entire setup using the Azure Automation (workflows).

I will be showing this at Microsoft Community Day on 23rd August, let's see if I can get that recorded.

[UPDATE] You can find the Script Snippet in entirety at below link:
https://gist.github.com/DexterPOSH/ae7ddcc6fa6aafacebc4

Resources:

http://michaelwasham.com

http://blogs.technet.com/b/keithmayer/archive/2014/08/15/scripts-to-tools-auto-provisioning-azure-virtual-networks-with-powershell-and-xml.aspx

http://blogs.blackmarble.co.uk/blogs/rhepworth/post/2014/03/03/Creating-Azure-Virtual-Networks-using-Powershell-and-XML.aspx

http://blogs.technet.com/b/kevinremde/archive/2013/01/19/create-a-windows-azure-network-using-powershell-31-days-of-servers-in-the-cloud-part-19-of-31.aspx


http://blogs.technet.com/b/keithmayer/archive/2014/04/04/step-by-step-getting-started-with-windows-azure-automation.aspx




Popular posts from this blog

Test connectivity via a specific network interface

Recently while working on a Private cloud implementation, I came across a scenario where I needed to test connectivity of a node to the AD/DNS via multiple network adapters.  Many of us would know that having multiple network routes is usually done to take care of redundancy. So that if a network adapter goes down, one can use the other network interface to reach out to the node. In order to make it easy for everyone to follow along, below is an analogy for the above scenario: My laptop has multiple network adapters (say Wi-Fi and Ethernet) connected to the same network. Now how do I test connectivity to a Server on the network only over say Wi-Fi network adapter?

PowerShell + SCCM : Run CM cmdlets remotely

Today I saw a tweet about using implicit remoting to load the Configuration Manager on my machine by Justin Mathews . It caught my eye as I have never really tried it, but theoretically it can be done. Note - The second tweet says "Cannot find a provider with the name CMSite", resolution to which is in the Troubleshooting section at the end.

PowerShell : Trust network share to load modules & ps1

Problem Do you have a central network share, where you store all the scripts or PowerShell modules ? What happens if you try to run the script from a network share ? or if you have scripts (local) which invoke scripts or import PowerShell modules stored on this network share ? Well you would see a security warning like below (Note - I have set execution policy as 'Unrestricted' not 'bypass' here): Run a .ps1 from the network share Well this is a similar warning, which you get when you download scripts from Internet. As the message says run Unblock-File cmdlet to unblock the script and then run it, let's try it.