Taking Control of VM Sprawl (Part 9)

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

In the previous article in this article, I showed you how to count the total number of virtual machines that were created or deleted on a server. That’s a good first step in getting a handle on VM sprawl, because it allows you to spot trends, but we can do a lot better than that. I mean, wouldn’t you like to know who is creating all of those virtual machines? In this article, I will show you how to do exactly that.

In my last article, I showed you the basics of using the Get-WinEvent cmdlet. I also explained that this cmdlet could be used to return specific event IDs such as those associated with VM creation. Of course the Get-WinEvent cmdlet is a general purpose cmdlet used to parse event logs. We just happen to be using it to track virtual machine creation. The reason why this little tidbit of information is important is because event logs contain a lot more information than just event IDs. There is a lot of information associated with each event. All we have to do is tell PowerShell to show us the events and the event details that are the most useful to us.

We started doing that in the previous article. You might recall this block of code:

$CreateEvents = Get-WinEvent –LogName Microsoft-Windows-Hyper-V-VMMS-Admin | Where {$_.ID –eq “13002”}

$DeleteEvents = Get-WinEvent –LogName Microsoft-Windows-Hyper-V-VMMS-Admin | Where {$_.ID –eq “13003”}



In this block of code, the $CreateEvents variable actually retrieves all of the event logs associated with virtual machine creation. The reason why you didn’t see those events is because we told PowerShell to count the events rather than displaying them. Similarly, the $DeleteEvents show all of the events associated with virtual machine deletion. With that in mind, let’s reuse the $CreateEvents variable and display the virtual machine name and the name of the user that created it.

Now because Get-WinEvent is a general purpose tool for querying event logs and is not specifically geared toward server virtualization there is no attribute that contains just the virtual machine name. We can however, look at the Message attribute which indicates that a new virtual machine was created and then tells us the name of that virtual machine. The name of the user who performed the action is stored in the UserID attribute. Consequently, we can display virtual machine creation messages and the user who created those virtual machines by using the following commands:

 $CreateEvents = Get-WinEvent –LogName Microsoft-Windows-Hyper-V-VMMS-Admin | Where {$_.ID –eq “13002”}

$CreateEvents | Select-Object Message, UserID

You can see the output from this command shown in Figure A.

Figure A: These are virtual machine creation events shown with the event message and the user who performed the event.

As you look at the figure above, you will immediately notice a problem. The user ID is displayed as a SID, not as a user name. This might not be a lot of help if we need to find out who created a particular VM. Fortunately, PowerShell can help with this too. We just have to jump through a few hoops to get there.

One of the problems that we face is that when we displayed the UserID in the previous figure, Windows showed us the SID that is associated with the user. However, the UserID is made up of more than just the SID. To show you what I mean, check out these three lines of code:

$CreateEvents = Get-WinEvent -LogName Microsoft-Windows-Hyper-V-VMMS-Admin | Where {$_.ID -EQ “13002”}

$ObjSID = $CreateEvents.UserID


The $CreateEvents line is exactly the same as what we have been using already. The $ObjSID = CreateEvents.UserID captures the UserID associated with the event and places it in a variable named $ObjSID, which the last line of code displays. If you look at Figure B you can see that the UserID is actually made up of a binary length, an account domain SID, and a value.

Figure B: The UserID contains multiple pieces of information.

If this code is to be of any use to us, we need to isolate the value. To do this, all we have to do is attach .Value to the end of the second line of code. The line should look like this:

$ObjSID = $CreateEvents.UserID.Value

Now, if I run the code only the SID is displayed, as shown in Figure C.

Figure C: Now, only the SID is displayed.

So now that we have isolated the SID, we can convert the SID into a user name. For this, we will need some more code.  Here is what the full code block looks like:

Get-WinEvent -LogName Microsoft-Windows-Hyper-V-VMMS-Admin | Where {$_.ID -EQ “13002”} | ForEach-Object -Process {

$ObjSID = $_.UserID.Value;


$Object = New-Object System.Security.Principal.SecurityIdentifier ($ObjSID)

$ObjUser = $Object.Translate([System.Security.Principal.NTAccount])



The first line of code is our Get-WinEvent line. Notice that we aren’t assigning this to a variable. Instead, we are piping the command’s output into a ForEach-Object command. The idea is that we have several tasks that we need to process for every event log entry that is returned.

The second line of code creates a variable named $ObjSID. This line is somewhat different from the previous code block. All I am doing here is extracting the user’s SID from the UserID that is associated with the event log entry.

The third line of code merely displays the contents of the $ObjSID variable. We don’t really need this command. I have simply included it as a way of verifying that the variable does indeed contain the user’s SID.

The fourth line of code is where things get a little bit tricky. I am creating a brand new variable called $Object. The reason why I am doing this is because it isn’t enough to simply display a user’s SID. We actually have to tell PowerShell that the value is a SID. Otherwise we won’t be able to translate that SID to a user name. So this line of code creates a variable named $Object and uses the New-Object command to turn the text value that was returned by $_.UserID.Value; into a machine recognizable SID.

Now that PowerShell realizes that this value is a SID, we can convert the SID into a user name. The fifth line of code does this by creating a variable named $ObjUser and then translating the $Object variable into a user name.

Finally, the $ObjUser.value line displays the resulting user name.

So when I run the code, Windows will display the SID for each returned result, followed by the corresponding user ID. I haven’t yet added the virtual machine name to the output because I wanted to first focus on outputting the user name. You can see the output in Figure D.

Figure D: These are the SIDs and user names of the users who have created virtual machines.

In this case, the output doesn’t do much to help us with VM sprawl because all of the VMs were created by NT Authority\SYSTEM. Just imagine however, if this were an environment in which VMs were created by individual administrators.


In the next article in this series, I will build on this technique and show you how to make this report a bit more meaningful.

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

Leave a Comment

Your email address will not be published.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Scroll to Top