Category Archives: PowerShell

Azure Internal Load Balancer and the Windows Azure Pack

I’ve been working with a customer who have been developing their own custom portal for the Windows Azure Pack (WAP) and wanted to host WAP in Azure as they had two datacentres and wanted to ensure that WAP was hosted elsewhere. So in this design my Inframon colleagues and I decided to use Azure to host WAP, ADFS, SQL AlwaysOn for WAP and a few other components (not SMA and SPF though).

The design called for two servers hosting WAP (to start with) to be deployed in Azure and to have all the different WAP components (Tenant API, Tenant Public API, Admin API, etc.) load balanced using the Azure Internal Load Balancer. This also required that we changed the FQDN for the different WAP components and create a DNS entry pointing to the Azure Load Balancer’s IP address.

The diagram below shows the desired installation:

WAP-ILB

Whilst there is a great deal of documentation from Microsoft about how to use the Azure Internal Load Balancer to create a SQL AlwaysOn cluster – which was very useful as we needed one of those for WAP’s databases – there wasn’t very much about using it for anything else.

After many late nights trying to get the Azure Internal Load Balancer to do what we required we started to understand the issues we were having. We had configured the probe port to use the port that WAP was listening on (we didn’t need to change the default WAP port numbers as the customer was happy for them to remain as default as WAP wasn’t customer facing). For example to load balance the Admin API that is on port 30004 we created an Azure Endpoint using the following PowerShell:


Add-AzureInternalLoadBalancer -InternalLoadBalancerName "WAP" -ServiceName "WAP" -StaticVNetIPAddress 192.168.100.25 -SubnetName "WAP"

$Port = 30004

$WAP1 = Get-AzureVM -ServiceName "WAP" -Name "WAP1"
$WAP2 = Get-AzureVM -ServiceName "WAP" -Name "WAP2"

Add-AzureEndpoint -Name "WAP$Port" -Protocol tcp -LocalPort $Port -PublicPort $Port -DirectServerReturn $false -LBSetName "WAP$Port" -ProbePort $Port -ProbeProtocol http -ProbeIntervalInSeconds 15 -ProbeTimeoutInSeconds 31 -InternalLoadBalancerName WAP -ProbePath / -VM $WAP1

Add-AzureEndpoint -Name "WAP$Port" -Protocol tcp -LocalPort $Port -PublicPort $Port -DirectServerReturn $false -LBSetName "WAP$Port" -ProbePort $Port -ProbeProtocol http -ProbeIntervalInSeconds 15 -ProbeTimeoutInSeconds 31 -InternalLoadBalancerName WAP -ProbePath / -VM $WAP2

$WAP1 | Update-AzureVM
$WAP2 | Update-AzureVM

This refused to work.

Unfortunately there is no way (that I can find) to monitor the Azure Internal Load Balancer to find out what was happening with it and what errors, if any, it is receiving from load balanced servers. After much investigation we discovered that as we were using Server Name Indication in IIS the HTTP probe wasn’t working. This was because the probe wasn’t using the SNI name and was receiving back a HTTP 400 BAD REQUEST ERROR. The Azure Internal Load Balancer correctly interpreted this a service failure so the load balancer wouldn’t work.

To resolve this problem we changed the default web site in IIS to listen on port 40091, ensured there was no SNI configured and altered the PowerShell to this:

$ServiceName = ""
$InternalLoadBalancerName = ""
$InternalLoadBalancerIPAddr = ""
$SubnetName = ""

Add-AzureInternalLoadBalancer -InternalLoadBalancerName $InternalLoadBalancerName -ServiceName $ServiceName -StaticVNetIPAddress $InternalLoadBalancerIPAddr -SubnetName $SubnetName

#Get the VM configuration from Azure
$WAP1 = Get-AzureVM -ServiceName $ServiceName -Name "WAP1"
$WAP2 = Get-AzureVM -ServiceName $ServiceName -Name "WAP2"

#The list of ports to be load balanced
$Ports = @("30004","30005","30006","30020","30022","30071","30072","30081","30091")

#Iterate each port in the list creating a new endpoint within the Azure ILB for each VM and port
ForEach($Port in $Ports){

Add-AzureEndpoint -Name "WAP$Port" -Protocol tcp -LocalPort $Port -PublicPort $Port -DirectServerReturn $false -LBSetName "WAP$Port" -ProbePort 40091 -ProbeProtocol http -ProbeIntervalInSeconds 15 -ProbeTimeoutInSeconds 31 -InternalLoadBalancerName $InternalLoadBalancerName -ProbePath / -VM $WAP1
Add-AzureEndpoint -Name "WAP$Port" -Protocol tcp -LocalPort $Port -PublicPort $Port -DirectServerReturn $false -LBSetName "WAP$Port" -ProbePort 40091 -ProbeProtocol http -ProbeIntervalInSeconds 15 -ProbeTimeoutInSeconds 31 -InternalLoadBalancerName $InternalLoadBalancerName -ProbePath / -VM $WAP2

}

#Update the VMs in Azure with their new configuration
$WAP1 | Update-AzureVM
$WAP2 | Update-AzureVM

This resulted in a happy Azure Internal Load Balancer but not a happy WAP deployment…

As each WAP server had all of the required components, Tenant API, Tenant Public API, Admin API, etc. and the IP address of the FQDN was set to the IP address of Azure Internal Load Balancer WAP was unable to communicate with itself across components. The diagram below shows the problem:

Azure-ILB-Fail

After much deliberation on how to solve this, including moving away from the Azure Internal Load Balancer and using a 3rd party tool in Azure, it was decided that we should put an entry in each WAP server’s hosts file for the WAP FQDN to reference itself. This led to a happy deployment!

So if you’re going to use the Azure Load Internal Load Balancer for anything then make sure you understand that servers that sit behind it can’t communicate with the IP address of it. If we had WAP split into each separate component on different servers, in different subnets behind different Azure Internal Load Balancers then this would have been OK but for this customer it would’ve been too much!

Scripting Shared Nothing Live Migration

UPDATE: 16th September 2016 – Link to download fixed.

I was working with a customer recently to replace their existing Windows Server 2012 Hyper-V clusters and System Center 2012 SP1 Virtual Machine Manager (VMM) installation with new Windows Server 2012 Hyper-V clusters and System Center 2012 R2 Virtual Machine Manager installation.

The customer was concerned about downtime for moving their Virtual Machines (VMs) from their existing clusters to the new ones.

We looked at the option of using Shared Nothing Live Migration (SNLM) to move VMs between the clusters which whilst an option wasn’t entirely realistic due to having in excess of 250 VMs and the names of the Logical Switches were different so each VM took some time to manually process, and being a manual repetitive task is prone to errors. The customer thought they’d have to go through migrating roles, moving CSVs and taking down VMs etc. Whilst that doesn’t sound too bad I wanted to offer a better option.

So looking at the options in PowerShell it was obvious that Move-VM was the cmdlet I wanted to use. Looking at the parameters I found -CompatibilityReport which “Specifies a compatibility report which includes any adjustments required for the move.” my first thought was where do I get one of those from?

After a bit of digging on the internet I discovered Compare-VM which creates a Microsoft.Virtualization.Powershell.CompatibilityReport.

The Compatibility Report fundamentally contains information on what would happen if, in this case, wanted to move a VM from one host to another.

So running:

Compare-VM <VMName> -DestinationHost <DestinationServer> -DestinationStoragePath <DestinationStoragePath> -IncludeStorage

gave me a Compatibility Report with some incompatibilities listed… Again after some digging I determined what these incompatibilities meant and how to resolve them.

I could then run a Compare-VM -CompatibilityReport <VMReport> which essentially says, “if I did this to the VM would it work now?” As long as you get no incompatibilities all is good!

Once that completed we could use the Move-VM –CompatibilityReport <VMReport> function to move a VM from one host to another…

Now whilst all these Compare-VMs are underway the source VM is quite happy existing and running as normal.

So where is this going? After discussions with the customer I expanded the PowerShell script to cope with multiple VMs, check for Pass Through Disks, remove VMs from clusters, etc.

The basics of the script are that it requires several parameters:

  • SourceCluster – where are the VMs to move?
  • DestinationServer – where do you want to move the VMs to? (optional, if this isn’t specified then a random member of the destination cluster is chosen for each VM to be moved)
  • DestinationCluster – what cluster do you want to move the VMs to?
  • SwitchToConnectTo – what is the name of the Virtual Switch to use on the destination server/cluster? For example if you VM/VMs are connected to a virutal switch called LogSwitch1 but your new cluster uses a virtual switch named LogicalSwitch1 you would specifiy LogicalSwitch1 for this parameter.
  • DestinationStoragePath – where do you want to put the VM’s storage on the destination cluster?
  • VMsToMove – this is a list of the VMs to be moved
  • LogPath – the path to a file you want to log the progress of the script to <optional>

Whilst this script may seem a little limited it managed to save the customer a great deal of time in migrating their VMs from their old Hyper-V clusters to their new ones. It can be extended to put in different Storage Paths for differenet VMs, different Virtual Switches etc.

Move-VMusingSNLM

PowerShell Script Signing Script

Everyone knows that PowerShell is Microsoft’s “new” scripting language. It’s here to stay and frankly it is very powerful!

With the new features of remote execution, persistence through reboots, etc. I thought it was about time to get to grips with it and understand what it can do.

Execution Policies

This article on TechNet explains the different policies available. By default PowerShell scripting is disabled – this is a good thing! Executing random scripts is not a good idea!

So which one to use?

If you’re an IT Pro building scripts then realistically the only option is Unrestricted. However on end user machines you need control! If you don’t have a PKI infrastructure then I’d highly recommend setting it up (future blog article coming up?) In a domain environment I’d recommend using a GPO to set the execution policy to AllSigned. This way you get control over what scripts can run – no randomly downloaded files can run (unless they’re signed by a certificate you trust – unlikely).

Signing a Script

To sign a script is quite straight forward if you have a certificate that allows Code Signing:

certificate

To sign a script you’ll need to run the following:

$Cert = (dir cert:currentuser\my\ -CodeSigningCert) 
Set-AuthenticodeSignature <Filename> $Cert -TimestampServer http://timestamp.comodoca.com/authenticode

And this will give you a PowerShell file with a signature block at the end. This file can then be executed on any client that trusts the certificate (i.e. the certificate is in the Trusted Publishers container) and that the execution policy is set to at least AllSigned.

What if you’ve got many files to sign? What if you’ve got multiple Certificates?

There are many blogs showing you how to use Get-ChildItem/Get-Item etc. but what you just want a quick solution? This leads me to the main point of this post!

I got fed up having to manually sign each file and remember the exact syntax needed to select the right certificate to sign my script so I created a script to sign my scripts. The script requires a very basic folder structure:

<Root Folder>
    CodeSign.ps1
    <Folder called ToSign>
    <Folder called Signed>

So what does it do?

The script enumerates all the files in the ToSign folder, asks which one you want to sign (you can select All), finds an appropriate certificate in the Current User’s personal certificate store (if there is more than 1 appropriate certificate it will prompt you for which one to use), signs the script and moves it to the Signed folder. Simple as that.

#Get all files within the subfolder: ToSign
$Files = Get-ChildItem -Path ".\ToSign\*.ps1"
#Create Var to hold Certificate for signing PS files
$global:MYCERT = $null
If ($files.Count -gt 0)
{
    #Show user all files that have been found
    Write-Host ($files.Count) "script files have been found."
    $i = 1
    foreach ($file in $files)
    {
       Write-Host `t $i - $file.Name
       $i += 1
    }
    #Ask user which script to sign
    $ScriptToSign = Read-Host "Which script would you like to sign? Type 0 if none, A if all or C to Cancel"

    #Load VB assembly to get some useful functions 
    [reflection.assembly]::LoadWithPartialName("'Microsoft.VisualBasic")
    #Is the input numeric?
    If ([Microsoft.VisualBasic.Information]::isnumeric($ScriptToSign))
    {
        $fName = $Files[($ScriptToSign-1)].Name
        #Ask user which Certificate to user
        SelectCertificate
        #Sign the script file
        SignScript ($fName)
    }
    #Does the user want all scripts signed?
    elseif ($ScriptToSign.ToLower() -eq "a")
    {
        #Ask user which Certificate to user
        SelectCertificate
        #Iterate through each file in the folder
        foreach ($file in $files)
        {
            #Sign the script file
            SignScript ($file.Name)
            Write-Host "------------"
        }
    }
    #Something else entered that is not catered for!
    else {
        Write-Host "No script will be signed"
    }
}
#This function actually signs the script file
function SignScript ([string]$fileName){
    #Tell user which file is being signed
    Write-Host "About to sign " $fileName
    #Sign Script file
    Set-AuthenticodeSignature ".\ToSign\$fileName" $global:MYCERT -TimestampServer http://timestamp.comodoca.com/authenticode
    #Move to signed folder
    Move-Item ".\ToSign\$fileName" ".\Signed"
    Write-Host "Script signed and moved to Signed folder"
}

#Allows the user to select the certificate to sign with
function SelectCertificate {
    #Obtain all certicates in the user's personal certificates folder
    $certList=(dir cert:currentuser\my\ -CodeSigningCert)
    If ($certList -is [array])
    {
        #More than one suitable certificate has been found. List all the certificates to the user
        Write-Host ""        $i = 1
        foreach ($cert in $certList)
        {
            Write-Host `t $i - $cert.Subject "CERT"
            $i += 1
        }
        $CertToUse = Read-Host "Which Certificate would you like to use?"
        #Check the user has actually entered a number and is within range
        If ([Microsoft.VisualBasic.Information]::isnumeric($CertToUse) -and ($CertToUse -gt 0) -and ($CertToUse -lt $i)){
            $global:MYCERT = $certList[($CertToUse-1)]
        }
        else{
            Write-Host "Invalid input entered. Exiting Scipting signing script!"
            Exit
        }
    }
    #No suitable certificates have been found
    elseif (($certList -eq $null) -or ($certList.Length -eq 0))
    {
        Write-Host "ERROR: No code signing certificates have been found"
        Exit
    }
    #Only one suitable certificate has been found - will use this by default
    else{
        $global:MYCERT = $certList
    }
}

Further Applications of Signed Scripts

So what else can you do with a signed script? System Center Configuration Manger 2012 (inc. SP1) allows you to use PowerShell scripts for a variety of purposes including application deployment. So for example if you wanted to deploy Windows 8 RSAT tools and install them using a PowerShell script you could create your script, sign it and use it as the execution program for your PowerShell install (program line would be: powershell -File <filename.ps1>)

If you need to run a PowerShell script to determine whether or not an application is installed (for example to determine if a specific Windows feature has been installed as the result of a script execution) you could create your script file, use the script above to sign the script and then copy and paste the contents of the signed file into ConfigMgr.

If you’re using System Center Updates Publisher to sign updates you can use the same certificate (if you’ve got the private key)/template to sign PowerShell files.

Two things to remember:

  1. Manually sign the CodeSign.ps1 file (the snippet above shows you how)
  2. Distribute the certificate you use to sign the script files

Download Script from SkyDrive

%d bloggers like this: