Taking Control of VM Sprawl (Part 15)

by [Published on 11 May 2016 / Last Updated on 11 May 2016]

This article continues the discussion of VM sprawl by examining ways of passing remote data to local variables.

If you would like to read the other parts in this article series please go to:

In the previous article, we got incredibly close to completing a script that will analyze a year’s worth of virtual machine creation and deletion data across a collection of servers. There are just a few chores left to finish. These include:

  • Write code to tally the total number of VM creation and deletion events across all servers
  • Revise the code to analyze the entire year (right now we are only examining two months’ worth of data)
  • Output the data to a chart

At first glance, these tasks are deceptively simple. For example, tallying the total number of VM creation events for the year would seem to be a matter of creating a variable and then adding together all of the creation events from the various months and assigning the value to that variable. Perhaps the code would look something like this:

$NumTotalCreateEvents = $NumTotalCreateEvents + $NumJanCreateEvents + $NumFebCreateEvents

Unfortunately, things are not even close to being as simple as they appear. There are two issues that will keep the previously mentioned approach from working correctly. First, in order to capture the virtual machine creation and deletion events, we are using remote sessions. As such, the variables that we assign are not valid outside of those sessions.

Suppose for instance I were to establish a remote session to a server named Hyper-V-1, and then used the command $VMs=Get-VM to capture the names of all of the virtual machines running on that server. As soon as the remote session ends, the variable is no longer valid. The reason for this is because the variable existed within the remote session, not the local session.

The other problem that we run into is that tallying creation and deletion events in the previously described manner would require certain variables to be used in the main body of the script and in a function. PowerShell doesn’t work this way, at least not by default.

We can still accomplish what needs to be accomplished, but variable scopes are going to become tremendously important. Being that the script is already so complicated, I really don’t want to introduce variable scopes into an already complex script, and try to talk you through what is going on. Instead, I want to create a couple of short, demonstration scripts that I can use to introduce the concepts that we will be using. Once you understand what is going on, we will integrate those techniques into our existing script.

So let’s deal with the issue of the remote sessions first. As previously mentioned, you can assign a variable in a remote session, but that variable is normally valid only on the remote system. As such, we need a way of bringing the contents of a remote variable back to the local system on which the script is running.

To demonstrate the problem, take a look at the simple script below:

$Session = New-PSSession -ComputerName Hyper-V-3

Invoke-Command -Session $Session -ScriptBlock {

                $MyVar = Get-VM

                $MyVar | Select-Object Name}

$MyVar | Select-Object Name

This script establishes a remote session to a server named Hyper-V-3 and then retrieves a list of virtual machine names. Notice however, that the script is designed to display the virtual machine names twice, once inside of the script block, and once outside of the script block. When I run the script however, the VM names are displayed only once. The reason is because the $MyVar variable is null within the local session. You can see what this looks like in Figure A.

Image
Figure A: The virtual machine names are displayed only once.

So what we need is a way of linking a remote variable to a local variable. I can accomplish this with the script below:

$Session = New-PSSession -ComputerName Hyper-V-3

Invoke-Command -Session $Session -ScriptBlock {

                $MyVar = Get-VM

                $MyVar | Select-Object Name}

$LocalResult = Invoke-Command -Session $Session -ScriptBlock {$MyVar}

$LocalResult | Select-Object Name

As you can see in Figure B, the virtual machine names are now output twice – once locally and once remotely. The trick to making this work is the second to the last line, which assigns the contents of a remote variable to a local variable.

Image
Figure B: The contents of the remote variable are now locally accessible.

OK, so I have solved the problem of capturing the contents of a remote variable. But what about the function issue? As you may recall, our original script passed server names to a function that would retrieve the VM count for each server.

With this in mind, take a look at the script below. This script is identical to the script that I just showed you, except that I placed the code into a function named GetMyData. After running the function, I am attempting to display the contents of the $LocalResult variable one more time.

Function GetMyData{

$Session = New-PSSession -ComputerName Hyper-V-3

Invoke-Command -Session $Session -ScriptBlock {

                $MyVar = Get-VM

                $MyVar | Select-Object Name}

$LocalResult = Invoke-Command -Session $Session -ScriptBlock {$MyVar}

$LocalResult | Select-Object Name}

GetMyData

$LocalResult | Select-Object Name

If you look at the output in Figure C however, you can see that the $LocalResult variable does not work outside of the function. The function returns local and remote results, but the variable is null outside of the function.

Image
Figure C: Results are only returned from within the function.

There are a few different ways to fix this problem, but believe it or not, I can fix the problem with a single word – Global. Here is my fix:

Function GetMyData{

$Session = New-PSSession -ComputerName Hyper-V-3

Invoke-Command -Session $Session -ScriptBlock {

                $MyVar = Get-VM

                $MyVar | Select-Object Name}

$Global:LocalResult = Invoke-Command -Session $Session -ScriptBlock {$MyVar}

$LocalResult | Select-Object Name}

GetMyData

$LocalResult | Select-Object Name

Now when I run the script, the correct output is displayed, as shown in Figure D.

Image
Figure D: The $LocalResult variable is now valid both inside and outside of the script.

OK, so how did I do this? My fix was to make $LocalResult a global variable. Notice that when I declared the variable I used this line of code:

$Global:LocalResult = Invoke-Command -Session $Session -ScriptBlock {$MyVar}

Inserting Global: in front of the variable name makes the variable’s value carry through the entire script. You will notice that when I am outputting the variable’s value, I can still refer to the variable as $LocalResult. It is only when I declare the variable that I have to refer to it as $Global:LocalResult.

This actually brings up an important point. In most programming languages, declaring a variable as Global is enough. In PowerShell however, we have to tell PowerShell that the variable is global any time that we change the variable’s value.

Conclusion

As you can see, we have our work cut out for us in the next article. We’ve got a script that is already complicated, and we are going to need to revise it so that some variables are global and so that the virtual machine creation and deletion counts that we extract from remote systems can be used locally. Things are going to get a little bit messy, but bear with me and we will make this work.

If you would like to read the other parts in this article series please go to:

See Also


The Author — Brien M. Posey

Brien M. Posey avatar

Brien Posey is an MCSE and has won the Microsoft MVP award for the last few years. Brien has written well over 4,000 technical articles and written or contributed material to 27 books.

Advertisement

Featured Links