Since I work at home, I rely a great deal on my Hyper-V environment. I'm assuming if you are using Hyper-V at work the same is true for you. Because I do a lot of testing, it is difficult sometimes to remember what is running on a given VM. Did I update that box to PowerShell v5? Is that VM running Windows Server 2016 TP 3 or TP4?
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Hyper-V virtual machines have a setting where you can keep notes which seems like the ideal place to store system information. Since most of my Windows machines are on my public network and the virtual machine name is the same as the computer name, I can easily use PowerShell remoting to connect to each virtual machine, get some system information, and update the corresponding note.
I can run a command like this to get the system information I need.
Get-WmiObject win32_Operatingsystem | Select @{Name="OperatingSystem";Expression={$_.Caption}}, @{Name="ServicePack";Expression={$_.CSDVersion}}, @{Name="PSVersion";Expression= {$psversiontable.PSVersion}}, @{Name="Hostname";Expression={ If (Get-Command Resolve-DNSName -ErrorAction SilentlyContinue) { (Resolve-DnsName -Name $env:computername -Type A).Name } else { [system.net.dns]::Resolve($env:computername).hostname } }}
I get back a result like this:
In my code, I want to include the computer name just in case it is different. If the server has the Resolve-DNSName cmdlet, I invoke it otherwise I use the .NET Framework to resolve the name.
To set the the Notes property , I can use Set-VM.
$VM = get-vm chi-dc02 Set-VM $VM -Notes $newNote
Be aware that this behavior will replace any existing notes. In my final code, I take that into account and by default I append the system information. But there is a parameter to replace the note contents if you wish.
My final code also includes a parameter to use the VM's IP address instead of it's name. I have a few VMs that are not part of my test domain, but I register their names in my DNS.
Here's the complete script.
#requires -version 4.0 #requires -module Hyper-V <# Update the Hyper-V VM Note with system information The script will make a PowerShell remoting connection to each virtual machine using the VM name as the computer name. If that is not the case, you can use the detected IP address and then connect to the resolved host name. Alternate credentials are supported for the remoting connection. The default behavior is to append the information unless you use -Replace. The default is all virtual machines on a given server, but you can specify an individual VM or use a wild card. You can only update virtual machines that are currently running. Usage: c:\scripts\Update-VMNote chi* -computername chi-hvr2 -credential globomantics\administrator -replace #> [cmdletbinding(SupportsShouldProcess)] Param( [Parameter(Position=0)] [ValidateNotNullorEmpty()] [Alias("name")] [string]$VMName = "*", [Alias("CN")] [string]$Computername = $env:COMPUTERNAME, [Alias("RunAs")] [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, [Switch]$ResolveIP, [Switch]$Replace ) Write-Verbose "Starting: $($MyInvocation.Mycommand)" Write-Verbose "Getting running VMs from $($Computername.ToUpper())" $VMs = (Get-VM -Name $VMName -computername $computername).Where({$_.state -eq 'running'}) if ($VMs) { foreach ($VM in $VMs) { Write-Host "Processing $($VM.Name)" -ForegroundColor Green if ($ResolveIP) { #get IP Address $IP = (Get-VMNetworkAdapter $VM).IPAddresses | where {$_ -match "\d{1,3}\." } | select -first 1 #resolve IP address to name $named = (Resolve-DnsName -Name $IP).NameHost } else { #use VMname $named = $VM.name } #get PSVersion #get Operating System and service pack #resolving hostname locally using .NET because not all machines #may have proper cmdlets $sb = { Get-WmiObject win32_Operatingsystem | Select @{Name="OperatingSystem";Expression={$_.Caption}}, @{Name="ServicePack";Expression={$_.CSDVersion}}, @{Name="PSVersion";Expression= {$psversiontable.PSVersion}}, @{Name="Hostname";Expression={ If (Get-Command Resolve-DNSName -ErrorAction SilentlyContinue) { (Resolve-DnsName -Name $env:computername -Type A).Name } else { [system.net.dns]::Resolve($env:computername).hostname } }} } #close scriptblock #create a hashtable of parameters to splat to Invoke-Command $icmHash = @{ ErrorAction = "Stop" Computername = $Named Scriptblock = $sb } #add credential if specified if ($Credential.username) { $icmHash.Add("Credential",$Credential) } Try { #run remoting command Write-Verbose "Getting remote information" $Info = Invoke-Command @icmHash | Select * -ExcludeProperty RunspaceID #update Note Write-Verbose "`n$(($info | out-string).Trim())" if ($Replace) { Write-Verbose "Replacing VM Note" $newNote = ($info | out-string).Trim() } else { Write-Verbose "Appending VM Note" $current = $VM.Notes $newNote = $Current + "`n" + ($info | out-string).Trim() } Set-VM $VM -Notes $newNote #reset variable Remove-Variable Info } #try Catch { Write-Warning "[$($VM.Name)] Failed to get guest information. $($_.exception.message)" } #catch } #foreach VM } #if running VMs found else { Write-Warning "Failed to find any matching running virtual machines on $Computername" } Write-Verbose "Ending: $($MyInvocation.Mycommand)"
Note that this is a script and not a function. I can now easily update my virtual machines.
C:\scripts\Update-VMNote.ps1 chi* -Computername chi-hvr2 -Credential globomantics\administrator -replace
And here's the result:
Of course you can modify the script to include any information you want in the note.
If you find this useful I hope you'll let me know.
Enjoy!
UPDATE JANUARY 11, 2016
I have updated the script and turned it into a function. You can now pipe virtual machines into the function. I also included a Passthru parameter so you can see the Note information. The function is hosted on GitHub at https://gist.github.com/jdhitsolutions/6f17c1d901ff870ff7a3.
Found it useful! I use the notes to put numbers on the VM’s to specify their priority.
Then I use a function that wakes up an entire environment on VMs at once. Lets say “All the machines whose name starts with NY, which means they are from the NY domain”
Priority 1 – Its a domain controller/dns/dhcp. I start it up and wait 20 secs before moving on to Priority 2 machines. Just to make sure the DCs are running when the rest of the servers wake up.
Priority 2 – Databases (I have services that depend on them). Wait 10 secs before moving to Priority 3
Priority 3 – The rest
If you know a better way to do this, please let me know 🙂
There is a setting for Automatic Startup Delay which is probably the best option. You would set delays starting from the beginning. Priority 1 would have a 0 second delay. P2 = 20 seconds, P3 = 30 seconds.