tech·nic·al·ly agile

Remote Execute PowerShell against each Windows 8 VM

Learn how to remotely execute PowerShell scripts on Windows 8 VMs using Hyper-V, streamlining updates and management with minimal effort.

Published on
9 minute read
Image
https://nkdagility.com/resources/T_5NKsLxoK7

Running a remote execute PowerShell against each Windows 8 VM on your Hyper-V host can help you maintain the guest VM’s in a minimal amount of time.

With all of the work going on at the office around PowerShell I wanted to have a go and solve another problem I have. I kept running into an issue when using Hyper-V on my local computer. Every so often, to keep them relent, I need to boot each of the virtual machines and run windows updates. My first thought was to create a PowerShell that would update the them automatically, by then I thought… why can’t I push the script out to each of them.

This will require two distinct Scripts.

Remote Execute against each VM

I am using Windows 8 with Hyper-V so all of the virtual machines are local. So the first step is to get a list of the VM’s and loop though them.

1$VMs = Get-VM
2foreach ($vm in $VMs)
3{
4    Write-Host  " $($vm.Name)" -ForegroundColor Yellow
5}

Note Watch out as Get-VM does not error out when you are not running under elevated privileges it just returns no machines. Now that vexed me for a while.

You can combat this by doing a check for elevated privileges and starting a new elevated process if your are not currently running in elevated mode.

1If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
2{
3    $arguments = "& '" + $myinvocation.mycommand.definition + "'"
4    Start-Process powershell -Verb runAs -ArgumentList $arguments
5    Break
6}

We will see how this works out remotely, but that’s for later.

So for each VM we can now execute something. However, what I want to do is run the script on my host (My main computer) and have it execute a portion of that script on the VM. For this I choose to have a separate ps1 file that did all of the local actions. There are really two ways to do this, first with a “[remote region]” that executes anything within that code block on the remote VM and second with a “Invoke-Command -ComputerName [computerName]”. The former sounds like it will complicate my scripts, making them too long, so I went with the latter. “Invoke-Command” has an option to pass another ps1 that does the actual execution which in turn allows me test that script separately.

So what do I end up with?

1Try{
2    Invoke-Command -ComputerName $compName -ScriptBlock {Set-ExecutionPolicy Unrestricted} -Credential $Cred
3    Invoke-Command -ComputerName $compName -FilePath $RemoteScript -Credential $Cred
4}
5
6catch {
7    "any other undefined errors"
8    $error[0]
9}

You will note that I have set the execution policy to ‘unrestricted’ prior to executing the code. As I am not signing the PowerShell scripts I need to do that in order to get them to run. Although all of the guest VM’s are within a domain my local computer, the one running the master script, is not. This does pose a number of interesting issues that means that you need to pass a ‘-Credential’ object into the invoke method.

1$Username = 'nakedalmmrhinsh'
2$Password = 'P2ssw0rd'
3$pass = ConvertTo-SecureString -AsPlainText $Password -Force
4$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass

This object will then allow the ‘Invoke-Command’ to authenticate correctly with the remote server. However the first time that you run this script you might get an error because the local computer is not trusted. If it was joined to the domain this would not be an issue but as it is in a workgroup the domain joined VM will reject you.

1Set-Item wsman:localhostclienttrustedhosts *

Just run the above on the VM to prep it or you can explicitly trust the host name. As these are all demo and not production VM’s I am not particularly worried about locking them down.

Now we can loop through the VM’s and execute a remote script against them. This then sounds like we are nearly done, however what happens when the VM’s are off or saved or paused. Well… nothing as you can’t execute a script against a machine that is not running. I needed to find a way to start the machines and luckily hyper-v can be totally managed by PowerShell and thus there is a command for that. The current state of the machine is stored in “$vm.State” which has a number of vaues that we need to do different things for.

 1switch ($vm.State)
 2{
 3     default {
 4          Write-Host  " Don't need to do anything with $($compName) as it is $($vm.State) "
 5     }
 6     "Paused"
 7     {
 8          Write-Host  "$($vm.Name) is paused so making it go "
 9          Resume-VM –Name *$compName* -Confirm:$false
10     }
11     "Saved"
12     {
13          Write-Host  "$($vm.Name) is paused so making it go "
14          Start-VM –Name *$compName* -Confirm:$false
15      }
16     "Off"
17     {
18          Write-Host  "$($vm.Name) is stopped so making it go "
19          Start-VM –Name *$compName* -Confirm:$false
20     }
21}

Here I use a case statement to choose what to do with each VM based on its current state. Therefore if it is ‘paused’ I need to ‘resume’ it to the running state. In addition I also store the current state in a variable so that I can reset each VM to its original state after I have run the script. This would allow me to boot each VM in turn rather than all at one and thus not overload my local computer.

So there is one last thing to do. When you change the state of  VM using “Start-VM” it can take some time to load. You could guess and insert a delay but what if the VM takes a long time to do a group policy update or maybe a windows update needs to finish.

1do {
2     Start-Sleep -milliseconds 100
3     Write-Progress -activity "Waiting for VM to become responsive" -SecondsRemaining -1
4}
5until ((Get-VMIntegrationService $vm | ?{$_.name -eq "Heartbeat"}).PrimaryStatusDescription -eq "OK")
6Write-Progress -activity "Waiting for VM to become responsive" -SecondsRemaining -1 -Completed

So I monitor the ‘heartbeat’ of the VM to determine when it becomes available. This will return “OK” once the VM has booted far enough to start responding to Hyper-V. This should mean that the VM will now be responsive to commands. As this can take some time I also want some sort of progress bar to be visible to let anyone monitoring the script know that we are waiting. There is a built in ‘Write-Progress’ command that lets us do this and it renders in the appropriate form for the medium that the script is executing in.

 1$Username = 'nakedalmmrhinsh'
 2$Password = 'P2ssw0rd'
 3$nameRegex = "[d*][(?.*)](?.*)[(?.*)]"
 4$RemoteScript = D:DataUsersMrHinshDesktopcmdService-VM.ps1
 5#------------------------------------------------------------------------
 6If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
 7{
 8    $arguments = "& '" + $myinvocation.mycommand.definition + "'"
 9    Start-Process powershell -Verb runAs -ArgumentList $arguments
10    Break
11}
12#------------------------------------------------------------------------
13$pass = ConvertTo-SecureString -AsPlainText $Password -Force
14$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass
15$confirm = $false
16$VMs = Get-VM
17Write-Output "Found $($VMs.Count) virtual computers"
18foreach ($vm in $VMs)
19{
20    Write-Host  "Execute for $($vm.Name)" -ForegroundColor Yellow
21    $matches = [Regex]::Matches($vm.Name,$nameRegex)
22    if ($matches.count -gt 0)
23    {
24        $compName = $matches[0].groups["name"].value.Trim()
25
26        $startState = $vm.State
27        switch ($vm.State)
28        {
29            default {
30
31               Write-Host  " Don't need to do anything with $($compName) as it is $($vm.State) "
32            }
33            "Paused"
34            {
35                Write-Host  "$($vm.Name) is paused so making it go "
36                Resume-VM Name *$compName* -Confirm:$false
37            }
38            "Saved"
39            {
40                Write-Host  "$($vm.Name) is paused so making it go "
41                Start-VM Name *$compName* -Confirm:$false
42            }
43            "Off"
44            {
45                 Write-Host  "$($vm.Name) is stopped so making it go "
46                 Start-VM Name *$compName* -Confirm:$false
47            }
48        }
49
50        Write-Host  " Waiting for '$($compName)' to respond "
51        do {
52                Start-Sleep -milliseconds 100
53                Write-Progress -activity "Waiting for VM to become responsive" -SecondsRemaining -1
54            }
55        until ((Get-VMIntegrationService $vm | ?{$_.name -eq "Heartbeat"}).PrimaryStatusDescription -eq "OK")
56        Write-Progress -activity "Waiting for VM to become responsive" -SecondsRemaining -1 -Completed
57
58
59        Write-Host  " Remote execution for '$($compName)' :) "
60
61        Try{
62            Invoke-Command -ComputerName $compName -ScriptBlock {Set-ExecutionPolicy Unrestricted} -Credential $Cred
63            Invoke-Command -ComputerName $compName -FilePath $RemoteScript -Credential $Cred
64        }
65
66        catch {
67            "any other undefined errors"
68            $error[0]
69        }
70
71         switch ($startState)
72        {
73            "Off"
74            {
75                Stop-VM  Name *$compName* -Force -Confirm:$false
76            }
77            "Paused"
78            {
79                Suspend-VM Name *$compName* -Confirm:$false
80            }
81            "Saved"
82            {
83                Save-VM Name *$compName* -Confirm:$false
84            }
85            default {
86
87                Write-Host  " Leaving $($compName) running $startState "
88            }
89        }
90
91    }
92    else
93    {
94        Write-Host  "Unable to determin name of $($vm.Name) as it is in a crappy format. Needs to be '$nameRegex'" -ForegroundColor Red
95    }
96}

Be warned that this is my first pass at this script and more scarily my first actual PowerShell script. As such I have no idea if this is the right way to do things, but it does seam to work. It lets you execute another PowerShell script against each of my VM’s.

Execute against VM

The second script that I need will do all of the work to configure and update the VM after it has been started. This will be a remote execution script that installs anything that it needs as well as getting and installing all of the outstanding Microsoft Updates.

 1Param(
 2  [string]$computerName = $env:COMPUTERNAME
 3)
 4If (!(Test-Path C:Chocolatey))
 5{
 6    Write-Host  " Install Chocolatey "
 7    iex ((new-object net.webclient).DownloadString("http://bit.ly/psChocInstall"))
 8}
 9Else
10{
11    chocolatey update
12}
13cinst enablepsremoting
14cinst PSWindowsUpdate -Version 1.4.6
15Write-Output "--SERVICE"
16Write-Output "-Allow ping for whatever"
17netsh advfirewall firewall add rule name="All ICMP V4" dir=in action=allow protocol=icmpv4
18Write-Output "-Enable File and Print Sharing for possible Lab Management use"
19netsh advfirewall firewall set rule group=File and Printer Sharing new enable=Yes
20Write-Output "-Check all Updates"
21Import-Module PSWindowsUpdate
22Get-WUInstall -MicrosoftUpdate -IgnoreUserInput -acceptall -autoreboot -verbose

I will likely go though a bunch more iterations on this script but for right now it:

  1. Makes sure that Chocolatey is installed
  2. Installed Chocolatey packages including ‘PSWindowsUpdate’
  3. Validates some firewall changes that I need
  4. Downloads and installs all Microsoft Updates

This may change and I want to test out some hierarchical PowerShell script option, but until then this makes it easy to update all of my VM’s.

Conclusion

Although I have tinkered with PowerShell now and then this is the first executable script that I have written. I am still in copy/paste mode but I can sure see the value of learning and using PowerShell for everything from installing applications to configuring systems.

You can just about do anything with PowerShell that you like.

Windows Software Development System Configuration Install and Configuration
Comments

Related blog posts

No related videos found.

Connect with Martin Hinshelwood

If you've made it this far, it's worth connecting with our principal consultant and coach, Martin Hinshelwood, for a 30-minute 'ask me anything' call.

Our Happy Clients​

We partner with businesses across diverse industries, including finance, insurance, healthcare, pharmaceuticals, technology, engineering, transportation, hospitality, entertainment, legal, government, and military sectors.​

Milliman Logo
Akaditi Logo
ALS Life Sciences Logo
Cognizant Microsoft Business Group (MBG) Logo
ProgramUtvikling Logo
Kongsberg Maritime Logo
Deliotte Logo
Microsoft Logo
Bistech Logo
Jack Links Logo
Schlumberger Logo
Genus Breeding Ltd Logo
Qualco Logo
Hubtel Ghana Logo
Capita Secure Information Solutions Ltd Logo
Freadom Logo
MacDonald Humfrey (Automation) Ltd. Logo
Workday Logo
Department of Work and Pensions (UK) Logo
Nottingham County Council Logo
Ghana Police Service Logo
Washington Department of Transport Logo
Washington Department of Enterprise Services Logo
New Hampshire Supreme Court Logo
Ericson Logo

NIT A/S

Cognizant Microsoft Business Group (MBG) Logo
New Signature Logo
Illumina Logo
Microsoft Logo