Tuesday, May 16, 2017

Missing solutions in Azure OMS

I wanted to install the Service Map solution in my Azure OMS development workspace. But I couldn’t find it. Anywhere. It wasn’t in the Solutions Gallery. But I couldn’t think how else you would get it.

The documentation wasn’t helpful. The documentation assumes you already have it. It doesn’t explain how to get it. Google searches were fruitless.

With the help of Sam Cogan, Microsoft MVP, I finally figured it out.

Not all OMS solutions have been rolled out to all regions. If your OMS workspace is in a region which doesn’t have a particular solution available, it is just missing from the Solutions Gallery. And most annoying, there is no one region with all possible solutions.

In the Americas, most Azure services are rolled out to West US first. OMS is one of the exceptions. OMS services are rolled out to East US first. Mostly.

My OMS workspace was located in West Central US. As of this writing, Service Map and several other solutions are only available in East US, so I couldn’t see them.

I created a second OMS workspace in East US, opened the Solutions Gallery, and all of the “missing” solutions were there.

It is always easy to find things when you know where to look.

Saturday, May 13, 2017

Self-referencing formula in PowerShell

A formula in PowerShell can have a reference to another part of the same formula.

I saw a script that calculated a start time at the next quarter hour from the current time. The code used was cumbersome, so I naturally felt challenged to do better.

My first attempt was to do this. We get the current datetime with the seconds and milliseconds chopped off. (Normally I always use full parameter names, but I may make an exception here to declutter the code. I haven’t decided yet.) Then we add 15 minutes minus the current minutes modulus (remainder) 15.

$Date = Get-Date -S 0 -Mil 0
$Start = $Date.AddMinutes( 15 - $Date.Minute % 15 )

Not bad. But can we do it in one line? And not just a one-liner for the sake of a one-liner. That would be stupid and useless. We’re not willing to sacrifice readability here. Clarity is important. Can we do it elegantly in on line? Yes. we can.

One way is to use a pipeline to first create the date, which we can then reference twice in the ForEach scriptblock.

$Start = Get-Date -S 0 -Mil 0 | ForEach { $_.AddMinutes( 15 - $_.Minute % 15 ) }

Not a bad solution, but I like to avoid pipelines when they aren’t strictly necessary, and creating a pipeline to handle a single object instead of a collection would seem to fit the definition of an unnecessary pipeline.

There is another way.

If the setting of a variable is wrapped in parenthesis, the value of the variable is set, and then the value is returned.

This just sets the $Date.

$Date = Get-Date -S 0 -Mil 0

But this sets the $Date and returns the new value of $Date.

$Date = Get-Date -S 0 -Mil 0 )

So now we can do things to the returned value, and reference the value of $Date in the process. Like this:

$Start = ( $Date = Get-Date -S 0 -Mil 0 ).AddMinutes( 15 - $Date.Minute % 15 )



Wednesday, May 10, 2017

Azure Cloud Shell Preview

Azure Cloud Shell Preview was rolled out today. A coworker, Joe Behrens, noticed the new button in the toolbar in the upper right of the Azure portal.


Cool! we thought. Another place to do PowerShell. Or…wait. What?



Microsoft’s new browser based shell for managing Azure resources is BASH.

Wow. This IS the new Microsoft.

Don’t worry, though. PowerShell is “coming soon.”



Wow.

Monday, May 8, 2017

Open RDP access to Azure virtual machines from your specific location with PowerShell

A PowerShell utility script to get your current external internet IP address and modify the Azure network security groups for your Azure VM’s to allow you to make RDP connections.

I recently got to see an Azure OMS demo by Ryan Zoeller from the Microsoft Cloud and Enterprise Black Belt team. Ryan mentioned in passing the challenges of setting up remote access to demo VM’s. At one extreme, the networking can be configured to allow access from anywhere, which opens up a potential attack vector. At the other extreme, networking can be locked down tight, but that limits your ability to access them during a demo at a client’s site. Everything in between is complicated and requires actual work.

I wanted a better solution, so I built one. I’m not going to do any of the actual work in the middle ground, I’m just building a utility script to make more effective use of one of the extremes.

Lock down the Azure network security group(s) (NSGs) for your Azure VM’s. Then, if you need to connect to them from a new location, run the code below to make the necessary changes to the NSGs to allow you to connect.


This code assumes your PowerShell session is already connected to Azure and that you are in the right context (tenant and subscription).

First, we need to know what IP address we will appear to Azure to be coming from. Randall Degges saw a need and provided a solution, ipify.org, a free to use web API to get your external address.

# Get current external IP address
$IP = Invoke-RestMethod -Uri 'https://api.ipify.org?format=json' | Select -ExpandProperty IP

Then we create the [PSSecurityRule] object which describes the firewall hole we are going to open up on all of the network security groups. We could use the New-AzureRMNetworkSecurityRuleConfig command, but instead we are going to leverage PowerShell’s dynamic tying to cast a hashtable as the correct object type. This doesn’t work with all object classes in all circumstances, but it works here, and I prefer the syntax as more human readable than the alternative.

# Create a security rule allowing inbound RDP connections from the external IP address
$SecurityRule = [Microsoft.Azure.Commands.Network.Models.PSSecurityRule]@{
        Name                      = ( 'RDP-' + $IP.Replace( '.', '-' ) )
        Description               = "RDP access from $IP"
        Protocol                  = "Tcp"
        DestinationAddressPrefix  = "*"
        DestinationPortRange      = 3389
        SourceAddressPrefix       = "$IP/32"
        SourcePortRange           = "*"
        Access                    = "Allow"
        Direction                 = "Inbound" }

Next we get all of the Azure virtual machines, NICs assigned to a VM, and network security groups (NSGs) that exist in this context.

# Get the VMs, assciated NICs, and all Network Security Groups in the current context
$VMs  = Get-AzureRmVM
$NICs = Get-AzureRmNetworkInterface | Where ID -in $VMs.NetworkProfile.NetworkInterfaces.Id
$NSGs = Get-AzureRmNetworkSecurityGroup

Loop through the NSGs …

# For each network security group
ForEach ( $NSG in $NSGs )
    {

Determine if the NSG is in use by a VM. NSGs can be associated directly with a NIC or to a subnet. We compare the list of the NSG’s associated NICs (if any) with the collection of NICs attached to VMs. Then we compare the list of the NSG’s associated subnets (if any) with the list of subnets attached to NICs attached to VMs. (I tried using Compare-Object to do the comparisons, but Compare misbehaves when one the of objects may be an array with a Null element.)

    # Determine if $NSG is in use by a VM,
    # that is, if it is connected to a NIC for a VM,
    # or if it is connected to a subnet which is connected to a NIC for a VM.
    $NSGinUse = $False
    ForEach ( $NIC    in $NSG.NetworkInterfaces.Id ) { If ( $NIC    -in $NICs.Id                         ) { $NSGinUse = $True } }
    ForEach ( $Subnet in $NSG.Subnets.Id           ) { If ( $Subnet -in $NICs.IpConfigurations.Subnet.Id ) { $NSGinUse = $True } }

If this NSG is and use, and the new security rule doesn’t already exist…

    # If the network security group is in use by a VM and
    # this security rule has not been previously added...
    If ( $NSGinUse -and $NSG.SecurityRules.Name -notcontains $SecurityRule.Name )
        {

Find a priority that isn’t already in use by an existing rule. This is a weakness in this bit of code. We are going to stick the new security rule in a somewhat random spot in the list. Most of the time, this will work just fine. If for some reason you have an existing list of security rules that is large and/or complex, this may fail or fail to put the rule in a place on the list where it will actually be processed. In those cases, you will need to modify this line to match your environment.

But this will almost always work as is.

        # Find an available priority
        $Priority = 1..40 | ForEach { $_ * 100 } | Where { $_ -notin $NSG.SecurityRules.Priority } | Select -First 1
        $SecurityRule.Priority = $Priority

Add the rule to the security group.

        # Add the new rule to the security group
        $NSG.SecurityRules.Add( $SecurityRule )
        }

And finally, post the change to Azure.

    # Post the changes to Azure
    $Null = Set-AzureRmNetworkSecurityGroup -NetworkSecurityGroup $NSG
    }

Here it is all together.

# Get current external IP address
$IP = Invoke-RestMethod -Uri 'https://api.ipify.org?format=json' | Select -ExpandProperty IP

# Create a security rule allowing inbound RDP connections from the external IP address
$SecurityRule = [Microsoft.Azure.Commands.Network.Models.PSSecurityRule]@{
        Name                      = ( 'RDP-' + $IP.Replace( '.', '-' ) )
        Description               = "RDP access from $IP"
        Protocol                  = "Tcp"
        DestinationAddressPrefix  = "*"
        DestinationPortRange      = 3389
        SourceAddressPrefix       = "$IP/32"
        SourcePortRange           = "*"
        Access                    = "Allow"
        Direction                 = "Inbound" }

# Get the VMs, assciated NICs, and all Network Security Groups in the current context
$VMs  = Get-AzureRmVM
$NICs = Get-AzureRmNetworkInterface | Where ID -in $VMs.NetworkProfile.NetworkInterfaces.Id
$NSGs = Get-AzureRmNetworkSecurityGroup

# For each network security group
ForEach ( $NSG in $NSGs )
    {
    # Determine if $NSG is in use by a VM,
    # that is, if it is connected to a NIC for a VM,
    # or if it is connected to a subnet which is connected to a NIC for a VM.
    $NSGinUse = $False
    ForEach ( $NIC    in $NSG.NetworkInterfaces.Id ) { If ( $NIC    -in $NICs.Id                         ) { $NSGinUse = $True } }
    ForEach ( $Subnet in $NSG.Subnets.Id           ) { If ( $Subnet -in $NICs.IpConfigurations.Subnet.Id ) { $NSGinUse = $True } }

    # If the network security group is in use by a VM and
    # this security rule has not been previously added...
    If ( $NSGinUse -and $NSG.SecurityRules.Name -notcontains $SecurityRule.Name )
        {
        # Find an available priority
        # Note: This is a weakness in the script. With no understanding of the existing rules, the selected priority for the new rule is somewhat random.
        # In the rare circumstance where the existing rules are numerous and/or complex, this may result in the new rule being ignored.
        $Priority = 1..40 | ForEach { $_ * 100 } | Where { $_ -notin $NSG.SecurityRules.Priority } | Select -First 1
        $SecurityRule.Priority = $Priority

        # Add the new rule to the security group
        $NSG.SecurityRules.Add( $SecurityRule )

        # Post the changes to Azure
        $Null = Set-AzureRmNetworkSecurityGroup -NetworkSecurityGroup $NSG
        }
    }