Job I want to have

I was looking through my Google Drive recently, doing some cleanup and pruning. I came across a document I had created in June 2016, called “Job I want to have”.

I don’t remember creating this document at all. It’s contents are a job posting for an “Infrastructure Technology Analyst”, without any kind of reference to the original company.

Here’s a snippet of what it looked like:

In June 2016 I was feeling stagnant; lack of motivation, lack of direction. I looked at this posting and thought that it was a huge stretch, and that it may be so difficult to actually achieve enough skill to be able to fill a position like this.

Now I’m reflecting on this, and realize that I have this job – I do all of these things right now, and it didn’t take a monumental effort. It wasn’t hours and hours of study time, or money for certifications and courses. I’m not saying I didn’t have to work hard to learn, or that it was random chance that put me here. It was certainly time spent learning, but by doing; by embracing the challenges as I faced them and learning how to solve them with the focus of a goal in mind.

What it really required was for me to step outside of where I was comfortable, embrace the fear of uncertainty, and try. Try something new and something different; try a chance that the grass could actually be greener.

I’m glad I came across this because I needed a refresh in my mind of what my goal was and understanding that I have achieved it. I needed a reminder that the core of what I’m doing now is still fun and drives me to have the kind of career I want to have.

Perhaps its nearing time to set my sights on something a little scary again.

Azure NSG Update issue with PowerShell

Today I’m working on creating and updating Azure Network Security Group rules from the Az PowerShell module. However I hit a bit of a bump.

I’m trying to do something simple like add 1 rule to an existing NSG that has other rules:

# Find the security group where it matches a variable
$nsgs = Get-AzNetworkSecurityGroup -ResourceGroupName $rgname | where-object { $_.Name -like "$nsgsuffix" }
# For the selected security group, add a rule, and then apply it
$nsgs | add-aznetworksecurityruleconfig -Access Allow -DestinationAddressPrefix $ips -DestinationPortRange 443 `
 -Direction Outbound -name allowOut-To3rdParty_443 -Priority 400 `
-SourceAddressPrefix * -SourcePortRange * -Protocol * | Set-AzNetworkSecurityGroup

This NSG and other rules were previously deployed through TerraForm, and I want to add an “out-of-band” rule that isn’t tracked in the Terraform state.

When I run these commands, I get this error:

Required security rule parameters are missing for security rule with Id:
Security rule must specify either DestinationPortRange or DestinationPortRanges.

That’s super strange; when I look at that rule in the Portal, it has ports listed:

So I check my $nsgs variable that was previously populated, looking specifically for the SecurityRules property:

$nsgs.SecurityRules

What I find is that for any rule that the portal displays having multiple ports, it lists them just fine (see Destination Port Range property below):

Name                                 : test_test.test
Id                                   : /subscriptions/1/resourceGroups/rg/providers/Microsoft.Network
/networkSecurityGroups/db-nsg/securityRules/test_test.test
Etag                                 : W/"3a98e199-3bf6-4308-806c-8d84bc03723e"
ProvisioningState                    : Succeeded
Description                          :
Protocol                             : *
SourcePortRange                      : {*}
DestinationPortRange                 : {123, 456, 789}
SourceAddressPrefix                  : {10.8.1.}
DestinationAddressPrefix             : {10.8.1.}
SourceApplicationSecurityGroups      : []
DestinationApplicationSecurityGroups : []
Access                               : Allow
Priority                             : 109
Direction                            : Inbound

But for any rule that only has a SINGLE port shown in the portal, it is blank for that property:

Name                                 : web_.db
Id                                   : /subscriptions/1/resourceGroups/rg/providers/Microsoft.Network
/networkSecurityGroups/db-nsg/securityRules/web_.db
Etag                                 : W/"3a98e199-3bf6-4308-806c-8d84bc03723e"
ProvisioningState                    : Succeeded
Description                          :
Protocol                             : *
SourcePortRange                      : {*}
DestinationPortRange                 : {}
SourceAddressPrefix                  : {10.0.0.1, 10.0.0.2}
DestinationAddressPrefix             : {10.1.1.100}
SourceApplicationSecurityGroups      : []
DestinationApplicationSecurityGroups : []
Access                               : Allow
Priority                             : 107
Direction                            : Inbound

My first thought was “how is this possible”?

I think this is because for these single port rules, we’re using the following Terraform syntax:

destination_port_ranges    = ["${var.port-https}"]

Terraform is expecting a list, but we’re only passing a single value. Terraform doesn’t consider this invalid, and it is applied to Azure successfully. However the flaw is revealed when trying to use the “Set-AzNetworkSecurityGroup” cmdlet because that attempts to re-validate ALL rules on the NSG, not just the one that I added or modified.

If I change that Terraform property to destination_port_range (note the singular) then everything appears to work properly when using Az PowerShell afterwards.

Get Azure VM Uptime – sorta

Let’s say you have a few virtual machines in Azure, that should only be running for a limited period of time. You want to ensure that they are stopped after that period of time, but you aren’t the one responsible for the service running on the VMs, and as such, want to empower the owner(s) with both the responsibility and capability to determine when that period of time is finished; perhaps with just a bit of nudging…

Thinking about this scenario had me determine, “what if I could find out when a VM uptime exceeds a set value (say 14 days)” and notify someone that “hey, maybe you forgot to turn this off”.

Turns out there isn’t an accessible “uptime” property for an Azure VM. However, there is a time stamp property from the last state change, and that’s what we can use to enable this logic.

The key is to use “Get-AzVM” with the -Status switch. When you run Get-AzVM, you can either return the “model view” or the “instance view” of a collection of virtual machines, or an individual VM. The Microsoft Doc page for this cmdlet describes using the -Status switch to provide the “instance view” which is where we get the properties we’re after.

If you run it for a collection of VMs you can retrieve the PowerState property:

For an individual VM, you get the “Statuses” property, which contains a collection of items:

The first item within the “Statuses” collection is the “ProvisioningState”, which displays (from what I can gather) the most recent provisioning action taken on the VM (starting it, stopping/deallocating it), along with a Time property. The second item is the current PowerState of the VM.

Using this information, I’ve built the PowerShell below to grab all VMs within a subscription, and for each of them evaluate the status against a point-in-time, outputting results for those which are running and where the successful provisioning exceeds the age of that point-in-time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Select-AzSubscription 
$comparedate = (get-date).AddDays(-14)
$rg = "resourcegroup"
#Get the Instance view of a collection of virtual machines (returns the PowerState property)
$vms = get-azvm -status -resourcegroup $rg
 
#Iterate through the collection
foreach ($vm in $vms)
{
	# only check if the VM is running, because if it's off we don't care
	if ($vm.powerstate -ceq "VM running")
	{
		# Get the instance view of a single virtual machine (returns the "statuses" object)
		$foundvm = get-azvm -resourcegroup $vm.ResourceGroupName -name $vm.Name -status
		    #$foundvm.Statuses.Time
                # check if time since it was provisioned (in Statuses[0]) is greater than a value
		if ($foundvm.Statuses.Time -le $comparedate)
		{
			write-output "$($foundvm.name) : running longer than 14 days"
		}
	}
}

Using this PowerShell, you could modify the results to populate an array, and email it to a recipient. This could be placed into an Azure Automation runbook and ran on a schedule.

Run script inside Azure VM from PowerShell

Today I was working on a way to initialize and format disks inside an Azure VM from PowerShell, specifically outside the VM itself.

The two most common ways to accomplish this are:

One of my key requirements was to not have the VM itself go and grab the script file to run; I wanted to pass a scriptblock to my VM from PowerShell instead. This immediately ruled out Set-AzVMCustomScriptExtension, as it needs a file that is accessible to the VM in order to run.

I had originally thought the same about Invoke-AzVmRunCommand. The example I saw for the ‘-ScriptPath’ parameter were all from a storage account or something similar. However after testing, I found that this ScriptPath could be local to MY computer, where I was running the PowerShell, not the VM itself. The command will inject the contents of the script file into the VM.

In order to simulate the “scriptblock” effect without having too many files, I put my code into a script file at runtime (so that it would be wherever the user was running the prompt), referenced it, and then removed it afterwards, like this:

# Build a command that will be run inside the VM.
$remoteCommand =
@"
#Get first disk that is raw with the lowest disk number (because we may not know what number it will be)
# F volume
Get-Disk | Where-Object partitionstyle -eq 'raw' | Sort-Object Number | Select-Object -first 1 | Initialize-Disk -PartitionStyle GPT  -confirm:$false -PassThru |
New-Partition -DriveLetter 'F' -UseMaximumSize |
Format-Volume -FileSystem NTFS -NewFileSystemLabel "F volume" -Confirm:$false
"@
# Save the command to a local file
Set-Content -Path .\DriveCommand.ps1 -Value $remoteCommand
# Invoke the command on the VM, using the local file
Invoke-AzureRmVMRunCommand -Name $vm.name -ResourceGroupName $vm.ResourceGroupName -CommandId 'RunPowerShellScript' -ScriptPath .\DriveCommand.ps1
# Clean-up the local file
Remove-Item .\DriveCommand.ps1

 

PSKoans – Going deeper with PowerShell

I learned about PSKoans while attending a PowerShell Deep Dive put on by Mike Pfeiffer and Cloudskills.io. It came up in the chat from one of the attendees, while we were discussing tools to assist learning PowerShell and becoming more comfortable with writing tests for your code.

Getting started is super simple, as the instructions on the original GitHub repository describe. In less than 5 minutes I was running “Measure-Karma” to begin the journey of PSKoans.

I did have to run “Set-PSKoanLocation -path <localpath>” in order to get it to recognize the set of files I wanted, rather than creating new Koan files within c:\users\<username>\PSKoans. This was important to me, so that I could control the files that were actually being used through Git (since I forked the project to my own GitHub repository). This allows me to proceed with PSKoans on multiple computers by syncing that repository.

I’ve now gone through the first 3 files, and while I can see the value in this tool for confirming and discovering new depths of PowerShell knowledge, I definitely would not suggest it to a PowerShell beginner.

For someone who understands the structure of the primary PowerShell components and the capabilities of things like Get-Help and Get-Command, I think it would provide the right amount of challenge to understand what is being asked and find the solutions naturally.

 

I would strongly recommend “PowerShell in a Month of Lunches” for a PowerShell novice before embarking upon PSKoans.