Scripts & Tools Articles - Altaro DOJO | Hyper-V https://www.altaro.com/hyper-v Hyper-V guides, how-tos, tips, and expert advice for system admins and IT professionals Wed, 25 May 2022 03:05:16 +0000 en-US hourly 1 PsExec: The SysAdmin’s Swiss Army Knife https://www.altaro.com/hyper-v/psexec-sysadmins/ https://www.altaro.com/hyper-v/psexec-sysadmins/#comments Fri, 26 Feb 2021 09:25:38 +0000 https://www.altaro.com/hyper-v/?p=19636 PsExec is a powerful command-line utility that allows managing, administering, and configuring remote Windows hosts. Learn more about PsExec, and how it can be used.

The post PsExec: The SysAdmin’s Swiss Army Knife appeared first on Altaro DOJO | Hyper-V.

]]>

Over the years, Windows administrators have collected and used a wide range of tools that provide capabilities, features, and abilities not natively found in the Windows operating system. Remote administration has always been a vast space for third-party tools and other utilities to provide capabilities for administering remote Windows systems.

Many of these are pay for products. However, free tools provide extremely robust remote management of Windows systems, allowing IT administrators to execute commands and other activities remotely. One such tool that has been extremely popular among Windows administrators is PsExec. What is PsExec? How is PsExec used? Is it installed in Windows by default? How does it work with Windows 10? This guide will look at PsExec, the sysadmin’s Swiss Army knife, and see how and why this tool is so valuable.

What is PsExec?

Windows administrators have long needed programs to execute remote processes on other systems. Unlike Linux, with remote SSH connectivity and other native tools built-in, Windows is limited in its ability to remotely run commands aside from interacting with remote systems using Windows Remote Desktop. This means of “remotely” administering Windows environments is cumbersome, challenging, and anything but efficient at scale.

PsExec is a “telnet-like” application that allows executing processes and interacting with console applications without using an entire desktop session. It provides remote access to tools that you would not otherwise be able to interact with generally in Windows, such as the IPconfig command. It will enable the launching of interactive command-prompts on remote Windows systems. An additional benefit of PsExec remote cmd is that it does not require installing client software for remote connectivity on either the host or target client.

The PsExec tool is a member of the very well-known Sysinternal’s PsTools suite of tools. Windows Sysinternals is a suite of tools that contains over 70 free utilities that allow Windows administrators to monitor, manage, and troubleshoot Windows. The SysInternals tools were the brainchild of Mark Russinovich and Bryce Cogswell. The tools are still currently developed and maintained.

The compelling characteristics, ease of use, and functionality makes PsExec extremely versatile, capable and has grown into a tool of choice among Windows administrators for remote command execution. Now that we have a better understanding of what PsExec is and a few of its characteristics, let’s get into how to use PsExec effectively and a few use cases.

Run as privileged users

PsExec offers the ability for IT admins to easily run certain commands and troubleshooting tools as specially privileged users, such as the Local System account. So, if you receive a PsExec access denied error, you can easily change the context of running PsExec commands. Note the following example of running commands as the Local System account:

  • psexec -i -s cmd.exe

This PsExec run as system ability allows administrators to perform tasks under special contexts that are not easily achieved using other means. We will take a look at further examples of running as different users with PsExec a bit later.

Why not just use PowerShell?

At this time, can administrators not use new tools and capabilities such as PowerShell remoting in the environment instead of leveraging PsExec? In some environments, organizations may pivot to natively using PowerShell remoting and other newer remote capabilities. However, there may still be legacy operating systems found as part of the infrastructure due to certain business or software requirements in some current business environments.

PsExec supports Windows versions back to Windows XP and can be effectively used in all operating systems from XP to the current Windows 10 release. There may be challenges with using PowerShell remoting with legacy operating systems such as Windows XP. Also, this has to be enabled and can potentially be problematic. PsExec is a more traditional and straightforward process to connect, start a service, and start issuing commands.

In this case, using PowerShell may not be possible. Additionally, PsExec can also work with PowerShell scripts and cmdlets. Using PsExec PowerShell commands can provide the best of both worlds, in terms of interoperability and functionality.

Requirements for Using PsExec

The requirements for using and running PsExec are relatively simple since there isn’t an installation of the utility itself. However, there are a few things to keep in mind when using the PsExec utility. These include:

  • PsExec is a Windows-only solution that works between Windows computers
  • You will need a Windows host computer to use to connect to the target Windows hosts you want to manage
  • The admin$ share must be available on the target Windows system
  • You will need to ensure you have connectivity between the host you are running PsExec, and the target computer you want to manage. It includes:
    • Ensuring a firewall is not filtering traffic between your host and target
    • Enabling the File and Printer Sharing Windows Defender Firewall exception on the target Windows computer

Allowing File and Printer Sharing in the Windows Defender Firewall
Allowing File and Printer Sharing in the Windows Defender Firewall

Downloading and Installing PsExec

To begin using PsExec, you need to download the SysInternals PsExec tool and install this on your administrative workstation, from which you will be running it. You can download PsExec as part of the PsTools suite that is readily (and freely) available from Microsoft. Download PsTools from the following link:

The tools are downloaded as a .zip file containing the executables for the PS Tools suite. After extracting the PS Tools suite, you will see the list of available tools as part of the PS Tools suite. Below is a look at the directory after extracting the .zip file. You will see you have PsExec.exe and PsExec64.exe.

After extracting the PS Tools suite of tools to a directory
After extracting the PS Tools suite of tools to a directory

Installing PsExec

As mentioned earlier, there is no need to install the PS Tools suite tools individually, including the process to install PsExec. These are self-executing files that have everything they need as part of the executable itself. However, there are a few things you probably want to do after you download the PS Tools suite to your administrative workstation. These include the following:

  • Add the PS Tools directory to your Windows PATH configuration
  • Security implications and Antivirus exceptions

Let’s take these one-by-one and see why the above steps are essential.

Add PsExec to your Windows PATH configuration

To allow PsExec to have native built-in Windows utility behavior, you will want to add your PS Tools directory to your Windows PATH statement. What does this do? Adding the PS Tools directory to the PATH statement will make the tools easy to use and allow launching them without your focus in the directory containing the PS Tools suite executables. To do this, you will want to copy the PS Tools directory to a centralized, intuitive location, then add this location to your Windows PATH configuration.

Below is the PSTools folder containing the PS Tools utilities after being copied to the root of the C drive.

Copying the PSTools directory to the root of the C drive
Copying the PSTools directory to the root of the C drive

After copying the PSTools directory to the location you want to house them, you need to edit the System PATH variable under the Advanced System properties in either the Windows client or Windows Server operating system. You can get to the Advanced System properties by typing sysdm.cpl > Advanced > Environment Variables. Then edit the PATH variable under the System variables.

Editing the Windows System PATH variable to add the path to the PSTools directory
Editing the Windows System PATH variable to add the path to the PSTools directory

As you can see below, after adding the PATH variable configuration for the PSTools directory, we can now type psexec. Windows finds the executable without executing the command in the parent PSTools directory.

Testing the PATH variable by executing the PsExec command outside the parent directory
Testing the PATH variable by executing the PsExec command outside the parent directory

Security implications and antivirus exceptions

It is essential to consider the security around using the PsExec utility. While it is a powerful, useful, and helpful utility, it can be used for malicious means, as we will discuss. Any tool that is misused can lead to tremendous damage to a production system or environment. Only those who have experience running command-line tools in a cautious and well-tested manner should be allowed to interact with production systems using PsExec.

An important consideration to make with the PSTool suite of utilities, including PsExec is that many antivirus solutions flag PsExec and other PSTools utilities as malicious. Are the PSTools utilities malicious? PsExec and the other PSTools are not malicious tools. These have been developed under the purview of Microsoft and are legitimate tools under the SysInternals division. Why are these flagged?

A file flagged in the PSTools suite folder by a well-known antivirus vendor
A file flagged in the PSTools suite folder by a well-known antivirus vendor

Malware creators and malicious attackers are getting better and more sophisticated in using native or legitimate tools to compromise systems. If an attacker can use a built-in, legitimate, or known tool, these are most likely not going to be flagged as suspicious, even if used for malicious activity. Even with newer allow listing cybersecurity solutions, built-in or legitimate tools such as those found in the PSTools may be whitelisted by organizations. Attackers know this. They often use these tools not to be flagged as suspicious. It may take much longer for an activity involving legitimate tools to be seen as suspicious.

For example, the NotPetya ransomware uses the PsExec utility to copy and spread via PsExec to admin$ shares using remote WMI. The BitPaymer ransomware also uses the PsExec utility to copy and run malicious commands in environments to spread ransomware quickly and effectively.

A note from the tool’s creator, Mark Russinovich, describes the security implications of the tool this way:

“The last security note relates to viruses. Several viruses use PsExec to propagate within a network, and as a result, several major antivirus products flag PsExec as a Trojan horse program or a worm. Remember that PsExec works on remote systems only if it runs within an account that has administrator group membership on the remote system. In other words, unless the account from which you run it has administrative access to a remote system, PsExec won’t be able to execute a process on the remote system. In addition, PsExec’s functionality can be achieved in other ways; thus, PsExec is only a convenience for virus writers, who could otherwise easily implement the functionality that PsExec provides.”

In addition to carefully considering the credentials used, administrators should consider allow listing PsExec with application allow listing cybersecurity solutions or GPO app-locker settings so it can run from a specific workstation or set of workstations. In this way, if a malicious actor has compromised a particular set of credentials, there is only a specific workstation or environment where the credentials will allow running PsExec or any other administrative tool.

Other PsExec security to note

There are other security features and behaviors to make a note of with PsExec. The PsExec service uses Windows security and impersonates the account from which you run the PsExec utility on the local system. It is essential to understand the restrictions and caveats of impersonation from a PsExec standpoint.

By design, impersonation is somewhat restricted. Remote processes don’t have access to network resources, even if the account typically has access to those resources. Suppose the account being impersonated for connectivity does not have local administrative privileges on the remote system. In that case, PsExec allows specifying a different account to provide alternate user context for commands. To use alternate credentials with PsExec, there are a couple of switches for you to note:

  • -u specify the username – REMOTEMACHINEadministrator
  • -p specify the password for the alternate credentials – -p administrator_pass ipconfig
  • PsExec remote cmd as administrator – psexec -u REMOTEMACHINEadministrator -p mypassword ipconfig

To prevent an “over the shoulder” attack by typing in the password in plain sight, you can omit the -p switch, and PsExe will prompt for the password. When typing the password, the characters are not echoed to the screen output. It provides some flexibility in how passwords are entered for alternate credentials.

Running under the LOCAL SYSTEM account

Another option regarding user credentials is the ability to use the LOCAL SYSTEM account. The LOCAL SYSTEM account is an extremely powerful account that has permissions to interact with core Windows services and processes. Examples include Winlogon and the Local Security Authority Subsystem Service (LSASS) service. The Windows System Account is the only account that has the ability to access certain resources such as the HKEY_LOCAL_MACHINESAM registry values as well as the special-purpose System Volume Information directory on recent Windows systems. To run PsExec as the LOCAL SYSTEM account you pass the -s switch:

  • psexec -s \REMOTEHOST <command>

The LOCAL SYSTEM functionality is also useful when needing to run a process or console application in the context of the LOCAL SYSTEM account on a local computer. Another beneficial use case of the PsExec utility is it is an easy way to run a program under the LOCAL SYSTEM user. On your local Windows system, you can use the following to run a program under the LOCAL SYSTEM account:

  • psexec -s -i <program>

As you decide to use PsExec in your environment, you must consider cybersecurity carefully. PsExec is a robust and powerful tool that depends on administrator credentials when making connections to target machines. It is essential that administrator credentials are adequately protected and safeguards are put in place so that PsExec is run only from a particular location or workstation.

How does PsExec work?

You may wonder how PsExec works under the hood and how it provides the functionality it does to execute remote commands on target Windows computers. The workflow of PsExec looks like the following:

  1. PsExec begins by extracting from its executable image an embedded Windows service called Psexesvc
  2. The service is copied to the Admin$ share of the remote system
  3. Windows Service Control Manager API is then used to have a remote interface with the target computer
  4. The Psexesvc service is started on the remote system
  5. A named pipe called psexecsvc is created to which the PsExec utility connects to and sends commands to the remote target
  6. The remote executable you specify is launched along with the options you specify

Controlling the Psexesvc service

As mentioned, the Psexesvc service is installed on the remote computer. You can control the behaviour of the service on the remote target. The default behaviour of the Psexesvc looks like this:

  • The service waits for the executable to terminate then sends the exit code back to PsExec for printing out to the local console
  • Using the -d parameter (don’t wait), the service will exit after starting the executable

Understanding the workflow and how PsExec interacts with remote systems will help from a troubleshooting perspective and keep security in mind.

Exploring PsExec functionality

One of the first things you will want to do is explore the functionality of the PsExec utility. You can easily see the capabilities provided by the tool when you issue the psexec command without switches. As shown, PsExec has a healthy number of command-line switches to interact with the remote target and control the behaviour.

Usage: psexec [\computer[,computer2[,…] | @file]][-u user [-p psswd]][-n s][-r servicename][-h][-l][-s|-e][-x][-i [session]][-c [-f|-v]][-w directory][-d][-<priority>][-a n,n,…] cmd [arguments]

-a Separate processors on which the application can run with

commas where 1 is the lowest numbered CPU. For example,

to run the application on CPU 2 and CPU 4, enter:

-a 2,4”

-c Copy the specified program to the remote system for

execution. If you omit this option the application

must be in the system path on the remote system.

-d Don’t wait for process to terminate (non-interactive).

-e Does not load the specified account’s profile.

-f Copy the specified program even if the file already

exists on the remote system.

-i Run the program so that it interacts with the desktop of the

specified session on the remote system. If no session is

specified the process runs in the console session.

-h If the target system is Vista or higher, has the process

run with the account’s elevated token, if available.

-l Run process as limited user (strips the Administrators group

and allows only privileges assigned to the Users group).

On Windows Vista the process runs with Low Integrity.

-n Specifies timeout in seconds connecting to remote computers.

-p Specifies optional password for user name. If you omit this

you will be prompted to enter a hidden password.

-r Specifies the name of the remote service to create or interact.

with.

-s Run the remote process in the System account.

-u Specifies optional user name for login to remote

computer.

-v Copy the specified file only if it has a higher version number

or is newer on than the one on the remote system.

-w Set the working directory of the process (relative to

remote computer).

-x Display the UI on the Winlogon secure desktop (local system

only).

-arm Specifies the remote computer is of ARM architecture.

-priority Specifies -low, -belownormal, -abovenormal, -high or

-realtime to run the process at a different priority. Use

-background to run at low memory and I/O priority on Vista.

computer Direct PsExec to run the application on the remote

computer or computers specified. If you omit the computer

name PsExec runs the application on the local system,

and if you specify a wildcard (\*), PsExec runs the

command on all computers in the current domain.

@file PsExec will execute the command on each of the computers listed

in the file.

cmd Name of application to execute.

arguments Arguments to pass (note that file paths must be

absolute paths on the target system).

-accepteula This flag suppresses the display of the license dialog.

-nobanner Do not display the startup banner and copyright message.

 

You can enclose applications that have spaces in their name with

quotation marks e.g. psexec \marklap “c:long name app.exe”.

Input is only passed to the remote system when you press the enter

key, and typing Ctrl-C terminates the remote process.

 

If you omit a user name the process will run in the context of your

account on the remote system, but will not have access to network

resources (because it is impersonating). Specify a valid user name

in the DomainUser syntax if the remote process requires access

to network resources or to run in a different account. Note that

the password and command is encrypted in transit to the remote system.

 

Error codes returned by PsExec are specific to the applications you

execute, not PsExec.

How is PsExec Used?

How is PsExec Used – PsExec Examples ?

There are many ways that the PsExec command can be used. These include:

  • Running a specific remote command on a single computer
  • Running a generic command prompt or PowerShell prompt on a remote computer
  • Running a remote command against multiple computers
  • Reading computers from a file
  • Launching programs for remote end-users interactively

Running a remote command on a single computer

Now that we have a basic understanding of PsExec, what it is, and have explored the command line switches, let’s start looking at using PsExec with a few basic connectivity and functionality examples. In its most basic form, PsExec requires the following:

  1. PsExec commands and any PsExec options to be passed
  2. A target computer
  3. A command to run on the target

We are using PsExec to connect to a remote computer called win10remote and invoke the remote computer’s ipconfig command. The PsExec Windows 10 output to the console is the ipconfig output of the remote Windows 10 workstation.

Running the ipconfig command on a remote computer using PsExec
Running the ipconfig command on a remote computer using PsExec

Running a generic command prompt or PowerShell prompt on a remote computer

Initially, you probably first think about running a specific command on a remote Windows computer. However, one of the compelling use cases with PsExec is the ability to run a generic command prompt or PowerShell session on a remote computer. In this way, if you are needed to perform remote troubleshooting on a remote computer without knowing which specific command you may need, you can start with the generic command prompt or PowerShell session.

As you can see below, we are launching a PowerShell session on a remote host, then issuing commands on the target Windows host to begin troubleshooting. After starting the PowerShell prompt, we can launch commands such as Ipconfig, route print, etc. In this way, you do not have to begin a new PsExec session on the remote Windows host for each command you want to run.

Using a generic PowerShell prompt launched with PsExec for troubleshooting
Using a generic PowerShell prompt launched with PsExec for troubleshooting

Running a remote command against multiple computers

You may have thought earlier that you may need to create a batch script of some sort to loop through multiple remote computers if you have several on which you need to perform a remote command. However, this functionality is built into PsExec. To launch a remote command against multiple computers, use the following syntax:

  • psexec \computername1,computername2,computername3,computername4

PsExec will launch the remote command on all of the computer names listed in the UNC string separated by a comma.

Reading computers from a file

In case you were wondering if you can use PowerShell in conjunction with PsExec, yes, you can. One of the easy ways to demonstrate how you can feed information into PsExec from PowerShell is by enumerating computers from Active Directory to provide PsExec the list of computers for running remote commands. An easy example of this is using the Active Directory PowerShell Module to pipe out computers from Active Directory into a text file. Once you have the text file, you can then have PsExec read the text file to enumerate computers that have been exported from Active Directory.

The Active Directory PowerShell module is installed by installing the AD DS and AD LDS Tools under the Role Administration Tools on a Windows Server host.

Installing AD DS and AD LDS Tools to install the Active Directory PowerShell module
Installing AD DS and AD LDS Tools to install the Active Directory PowerShell module

Once you have the Active Directory PowerShell module installed, you can use it to create your input file for PsExec. What if you wanted to flush the DNS cache on all computer contained in a specific Active Directory OU? You could do something like the following:

  • psexec “\$((Get-AdComputer -Filter * -SearchBase “OU=TestOU, DC=cloud, DC=local”).Name -join ‘,’)” ipconfig /flushdns

The command above searches a particular Active Directory OU, joins the resulting computers into the comma-separated string format the PsExec command needs to connect to multiple remote computers, and then runs the Ipconfig /flushdns command.

Launching programs for remote end-users interactively

Another interesting switch with the PsExec utility allows launching programs on the remote Windows host interactively. In other words, the output will be seen on the remote computer and not on your local output. Suppose a user is having difficulty launching a remote program on their end-user client. In that case, you can connect using PsExec and launching the program session interactively on the remote Windows host.

To do that, you can use the following command:

  • psexec -i \remotewindows winword.exe

The command above will launch Microsoft Word interactively for the end-user to see the program launch on their Windows workstation. It is a great tool for helping remote end-users launch a particular program if unable to use their normal shortcuts.

To properly protect your Hyper-V virtual machines, use Altaro VM Backup to securely backup and replicate your virtual machines. We work hard perpetually to give our customers confidence in their Hyper-V backup strategy. 

To keep up to date with the latest Hyper-V best practices, become a member of the Hyper-V DOJO now (it’s free).

Concluding Thoughts

No matter how few or many Windows hosts you may need to administer remotely, there is always the need to connect to remote Windows systems to issue commands, perform troubleshooting, install software, and assist end-users. With this in mind, most organizations need to use tools to establish a connection to remote Windows clients and issue commands. Using Remote Desktop connections to remote Windows computers is cumbersome, time-consuming, and not very efficient.

PsExec is a powerful tool that is part of the PSTools suite of tools from SysInternals. It is a powerful utility that allows IT admins to connect to Windows hosts remotely and issue commands on the remote system. The requirements for connecting to remote Windows systems are minimal. These include credentials that allow connecting to the Admin$ share of the remote computer, File and Printer sharing enabled, and local administrator permissions if needed for configuration on the remote Windows host.

PsExec provides many different capabilities that allow you to interact with a single remote Windows host, multiple Windows computers, launching programs interactively, and even working in tandem with Windows PowerShell to pull a list of computers from Active Directory. Due to the nature of what PsExec can do, organizations need to be careful about who has access to run the utility and which locations it can be run. Malware, including many ransomware variants, can use PsExec to propagate malicious files and run specific commands on the network for compromising environments.

Organizations may want to hesitate to whitelist PsExec and other command-line tools globally since these can be used for malicious purposes. A better approach is to use a minimal scope of allowed workstations allowed to use PsExec to connect to remote Windows clients. In this way, the attack surface is much smaller if malicious software attempts to use known, common, or built-in command-line tools. All-in-all, PsExec is the sysadmin’s Swiss Army knife of functionality for performing remote administration from the command line and provides quick and easy access for troubleshooting, configuration, and assisting end-users.

The post PsExec: The SysAdmin’s Swiss Army Knife appeared first on Altaro DOJO | Hyper-V.

]]>
https://www.altaro.com/hyper-v/psexec-sysadmins/feed/ 1
What is UAC Virtualization? https://www.altaro.com/hyper-v/what-is-uac-virtualization/ https://www.altaro.com/hyper-v/what-is-uac-virtualization/#respond Thu, 29 Oct 2020 20:38:10 +0000 https://www.altaro.com/hyper-v/?p=19146 Everything you need to know about UAC Virtualization, a service intended to limit system-level changes to privileged Administrative accounts

The post What is UAC Virtualization? appeared first on Altaro DOJO | Hyper-V.

]]>

So you know what Virtualization is, and you know what UAC is, but what’s UAC Virtualization?

User Account Control (UAC) is more than just a Prompt

Most Windows users will have seen the User Account Control (UAC) execution prompts, that request the user to confirm if they really want to provide a piece of software with privileged permissions:

User Account Control UAC

User Account Control Windows Command Processor

Microsoft introduced UAC in Windows Vista (and Server 2008) to limit system-level changes to privileged administrative accounts only. Generally speaking, software shouldn’t need write access to system paths during normal operation, so Windows explicitly restricts them to only run in the user context, significantly improving security over prior operating systems like Windows XP. Without authorisation from an administrator, a software call to write to a system path would fail and the program couldn’t execute. If such access is required (such as installing an update) the UAC prompt requests the user to provide consent before system changes can be made.

This was a great innovation, from both a security and data management point of view. If the software could write to any location with no authorisation required, it could damage files or data, circumvent the operation of other programs, and install or modify anything it liked, all with no oversight and no way for the user to know that their computer was potentially being taken over and controlled by a rogue application. User data was also potentially scattered all over the place in obscure sub-directories within application paths prior to Microsoft forcibly ending such shoddy data management.

UAC breaks Legacy Software, so Microsoft finds a Workaround

The change Microsoft made stopped applications from writing to system files and folders unchecked, which included %ProgramFiles%, %Windir%, %Windir%system32, and the registry path HKEY_LOCAL_MACHINESoftware. Programs could make changes to the application path (for installation and updates) after user acknowledgement of a UAC prompt, but user settings and data should be stored within the user profile. Programs aren’t meant to be calling a UAC prompt every time they run (although there are still some terrible examples of this today). Even global settings should be stored in either a shared editable program space (%programdata%) or the appropriate user hive in Windows registry – nothing user-related should ever be written to the application or Windows folder paths.

However, Microsoft was aware that what they were doing with Vista was new and relatively sudden – rightly or wrongly, application developers had been writing software for years based on some fairly fundamental assumptions, and UAC was going to break some things – things lots of companies depended on to, well, not be broken. As with most major changes, a certain amount of transition was inevitable.

While this new way of limiting how software could alter important system files was an excellent idea, Microsoft now had the problem of supporting legacy applications – software that had been written before the time of enlightenment, in the dark ages when software developers would write user data within the application path (something they should have stopped doing after Windows 2000 introduced the Documents and Settings folder, with user data organised and segmented logically within the one root path). No amount of wishful thinking was going to rewrite the thousands of critical business applications that had been created in the ’90’s, or undo the poor coding practices that persisted right up until Vista’s launch at the end of 2006 (and beyond, in some cases, but let’s not dwell on that).

If legacy software would fail to run in this new, strict, UAC controlled environment, and that software was important, users would either run everything with administrative privileges (losing any improvement UAC was bringing to the table – indeed, running everything with admin rights would actually have been a worse situation than we’d had with XP) or disable UAC altogether (an equally bad outcome).

Please Welcome to the Stage: UAC Virtualization

So Microsoft came up with UAC Virtualization (or UAC Virtualisation, for those of us who speak real English). Because these applications expected to be able to write to a program file path, UAC Virtualization obfuscates the true path to the target folder for the application and presents it with a writable container within the user path (similar to a Symbolic Link or Junction). As far as the application is concerned it has write access to what it needs, but in reality instead of writing to C:Program Files<Application Path>, it’s actually writing to %LOCALAPPDATA%VirtualStore<Application Path> (where Windows makes a copy of all program path files the first time the application tries to write to them).

Interestingly, if the program has the ability to open a file browser it can expose to the user, you’ll find that as far as it’s concerned, it still thinks the files it’s working with are exactly where it expects them to be – in the C:Program Files<Application Path> directory tree. But from outside the application, you can see that it’s actually manipulating files within the Virtual Store – nothing you do from within the virtualized program will allow you to see the true path.

UAC Virtualization Only Works in Specific Circumstances

There are a few caveats to be aware of in order to make sure UAC Virtualization works:

  1. Limited to 32-bit applications only. AMD64 compatible applications have all been created after these fundamental design decisions were made and by their very nature can’t be written to address system files in ‘the old way’ that UAC Virtualization was created to address. (IA64 applications have their own special set of problems.)
  2. The user must have write access to the files in the original file path. Attempting to write to any files with read-only permissions would bring the whole house of cards crashing down (ie causes the application to crash with an error code).
  3. UAC Virtualization can’t be applied to applications running as administrator or otherwise elevated in any way – it must be running within the standard user context.
  4. UAC Virtualization is disabled by default – it has to be explicitly enabled.

Registry Virtualization?

Registry Virtualisation is a more specific term for UAC Virtualization applied specifically for registry calls. In that case, registry virtualization fails if an administrator can’t write to the key(s) being called (similar to requirement ‘B’ above).

A correct Application Manifest ensures UAC Virtualization isn’t Applied

UAC also makes use of the Application Manifest XML file. This is a line in the manifest file that tells Windows what access level the application needs to run at when executed. The options are

  • ‘asInvoker’ (same access token as the parent process, ie the current user context)
  • ‘highestAvailable’ (highest privileges the current user can obtain)
  • ‘requireAdministrator’ (can only run as administrator – this is the flag that calls the UAC prompt we all enjoy so much)

If none of these are present, Windows will virtualise the application automatically (assuming that behaviour has been enabled).

UAC Virtualization silently ensured everything kept working

UAC Virtualization was created to allow legacy applications to continue to function in the new UAC world, by having a way to automatically re-route file access requests from the old (incorrect) program path target to the user data path, completely transparent to both the user and the application. Just like all good IT solutions – elegant, completely unseen and almost certainly unknown to the possibly hundreds of thousands of people who would have relied on it every day for some years.

Not a Windows 10 Feature! (and don’t rely on it hanging around)

Some people have mistakenly thought UAC Virtualization was a Windows 10 feature, but it isn’t. It still exists, in the same way that innumerable legacy features continue to be carried forward and supported for the tiny number of edge cases that still need them. But it was a feature built a decade and a half ago for a problem that in most cases has been long since solved. Microsoft has always stated that it is a temporary, ‘interim application compatibility technology’ that will eventually be removed – it exists for the purpose of allowing non-compliant legacy applications to operate, but only as a stop-gap measure, and developers have been encouraged for many years to transition their programs to a compliant state (ie update or replace it with something that no longer attempts to write to sensitive system areas).

So how do You Make it Work?

UAC Virtualization Group Policy

Activation of UAC Virtualization is done within Group Policy. Browse to Computer Configuration, Policies, Windows Settings, Security Settings, Local Policies and Security Options. Scroll right to the bottom of the Security Options Policy window. The last policy is called “User Account Control: Virtualize file and registry write failures to per-user locations”:

Select the ‘Define this policy setting’ checkbox and change the radio button to ‘Enabled’:

User Account Control UAC Security Policy Setting

Don’t try this at Home

You’ll never need to do this, of course – if you’ve never heard of this feature before now, that’s because you’ve never come across a legacy application that needed it, and that’s extremely unlikely to change in your future career. But it doesn’t hurt to know what it was, and why it was necessary, if for no other reason than to appreciate that words like ‘virtualization’ can mean rather different things in different contexts. That, and how a single seemingly innocuous change in Group Policy can trigger such an elegant and complicated change to the way Windows treats running applications and can intercept and re-route their file calls on the fly under the hood. Makes you wonder what else you didn’t know about those fifty million lines of code… Like App-V, for example.

Conclusion

UAC Virtualization was essentially a workaround, to allow legacy programs that expected to store and change user data within the program’s application folder path, to continue to work under new, more secure and better-organised data structures introduced in Windows Vista alongside User Account Control security features. This applied to both files and registry targets and was achieved by dynamically redirecting file access calls from application paths to user data paths in a way that was completely transparent to the software. The term ‘virtualization’ here referred to the folders and data only appearing in the expected location ‘virtually’, when it was actually in a completely different location.

Have you had any hands-on experience with UAC Virtualization? Let us know in the comments section below.

The post What is UAC Virtualization? appeared first on Altaro DOJO | Hyper-V.

]]>
https://www.altaro.com/hyper-v/what-is-uac-virtualization/feed/ 0
Free Script – Convert Legacy Teamed Hyper-V vSwitch to SET https://www.altaro.com/hyper-v/free-script-vswitch-set/ https://www.altaro.com/hyper-v/free-script-vswitch-set/#comments Thu, 16 Jan 2020 16:25:25 +0000 https://www.altaro.com/hyper-v/?p=18335 Microsoft does not provide any way to convert an existing team configuration to SET. This free PowerShell script does exactly that. Enjoy!

The post Free Script – Convert Legacy Teamed Hyper-V vSwitch to SET appeared first on Altaro DOJO | Hyper-V.

]]>

With Windows Server 2012, Microsoft changed the networking game by introducing native teaming. It gave us a supported way to balance traffic for several virtual machines and, if desired, the host. In Windows Server 2016, Microsoft changed the game again with the switch-embedded team (SET). This singular logical construct combines the traffic balancing technology of a team into the Hyper-V virtual switch. It includes several other improvements, making it a worthwhile choice over the traditional team. Unfortunately, Microsoft does not provide any way to gracefully convert an existing team configuration to the new SET. I have solved this problem with a new PowerShell script.

What Benefits Does a Switch-Embedded Team Provide Over Native Teaming?

SET improves on the load-balancing failover (LBFO) team and virtual switch combination in two major ways: configuration simplicity and performance.

SET Configuration Simplicity

With the LBFO and team combination, you must create the team and virtual switch separately. Once created, you must manage them separately. Even experienced Hyper-V administrators can forget which construct implements a specific feature. With SET, you only need to create one item.

One point of contention: SET has no UI at this time. You can only create or manipulate a SET with PowerShell.

SET Performance Benefits

SET provides new technologies that allow them to outperform the older LBFO+team configuration:

  • Internal improvements, some stemming from the integration of teaming with the virtual switch into a single construct, others from technology maturation
  • LBFO cannot work with RDMA capabilities; SET can
  • SET introduces virtual machine multi-queue (VMMQ) capability. Among other things, VMMQ lifts the single-core restriction for VMQ. As a result, VNICs on SET do not have the same upper inbound traffic limit of about 5.5 Gbps as they do on LBFO.
  • The latest software-defined networking capabilities

Microsoft has a short article explaining SET’s benefits.

Who Should Not Convert to a Switch-Embedded Team?

For new installations, SET almost always makes more sense than LBFO. It doesn’t work for everyone, though. You also may not want to make changes mid-stream. I recommend that you take some time to ensure that the above benefits apply to your situation before deciding to use this script.

I can think of three situations where you should hesitate to switch from LBFO:

  • VMM installations. VMM does not deal with change well. It also does not have any way to ignore host conditions that it does not like. If you change the virtual switch on one host, VMM will exclude it from any clusters. You have options, such as temporarily removing hosts from VMM. Any process that you choose will take time and planning. Think it through before doing anything.
  • Hardware LAG installations. SET does not support static or LACP teaming modes. In my experience, most people do not understand LACP, incorrectly believing that it grants universal aggregation capability. Even without LACP, SET will result in a flat or increased performance profile in almost all cases. However, if you have one of the few cases where LACP enables your virtual adapters to exceed 10Gbps, then think twice about giving it up. Even if you don’t really benefit from LACP, moving to SET requires you to change the hardware configuration to allow a switch-independent team.
  • Gigabit installations. Most of SET’s enhanced powers benefit systems with 10 gigabit or faster adapters. Gigabit adapters do not cause significant processing load. You won’t see any improvements from SET. I would suggest that you should just leave your configuration as-is.

Script Warnings

If you use VMM, make certain to read the above note. This script can do everything that I promised and still cause problems for VMM.

Unfortunately, it might not work as promised. I performed a great deal of testing during development, and my final testing rounds all worked exactly as expected. However, early iterations caused a lot of problems. Some were significant, including one that I could only fix with a reinstall of Hyper-V. Even if I have managed to completely clear the worst of these problems, you might still encounter errors on your hosts that occur after it deletes your existing configuration but before it builds up a replacement.

As a precaution, record your existing configuration. You need to keep track of the team, virtual switch, and all management operating system virtual NIC settings. You can use my host configuration script as a starting ground to quickly rebuild your networking if necessary.

Script Explanation

I intended for this script to perform a complete replacement of your LBFO+virtual switch with a functionally identical SET configuration. That includes virtual NICs. As you may have already discovered, disconnecting a virtual NIC used by the management operating system destroys the vNIC. So, you can’t just temporarily disconnect them like you can with virtual machine vNICs. I took that in mind when building this script.

This script does the following:

  • Detects if the host belongs to a cluster; places nodes into maintenance mode and waits for VMs to drain
  • Disconnects remaining VMs from the virtual switch
  • Destroys the virtual switch(es) and team(s)
  • Creates a SET with the same settings as the original LBFO and switch (where applicable)
  • Recreates the original management OS vNICs and re-attaches them. Recreated settings include:
    • The IP address and gateway information
    • Advanced adapter settings (such as jumbo frames)
    • QoS settings
  • Reconnects VM network adapters
  • Ends maintenance mode on cluster members

It will only touch LBFO+virtual switch teams. It will skip over external virtual switches on single adapters, private switches, internal switches, and SET switches.

If you just run the script by name, it will auto-convert all LBFO+team combinations. It does have a few optional parameters. You can specify specific switches. You can give each switch a new name. You can change QoS and load-balancing modes.

Script Listing

I have included the complete 1.0 script listing below for your convenience. I will not maintain this listing. You will find any updates on my GitHub page. If you find problems, please open issues there.

<#
.SYNOPSIS
	Converts LBFO+Virtual Switch combinations to switch-embedded teams.
.DESCRIPTION
	Converts LBFO+Virtual Switch combinations to switch-embedded teams.
	Performs the following steps:
	1. Saves information about virtual switches and management OS vNICs (includes IPs, QoS settings, jumbo frame info, etc.)
	2. If system belongs to a cluster, sets to maintenance mode
	3. Disconnects attached virtual machine vNICs
	4. Deletes the virtual switch
	5. Deletes the LBFO team
	6. Creates switch-embedded team
	7. Recreates management OS vNICs
	8. Reconnects previously-attached virtual machine vNICs
	9. If system belongs to a cluster, ends maintenance mode
	If you do not specify any overriding parameters, the new switch uses the same settings as the original LBFO+team.
.PARAMETER Id
	The unique identifier(s) for the virtual switch(es) to convert.
.PARAMETER Name
	The name(s) of the virtual switch(es) to convert.
.PARAMETER VMSwitch
	The virtual switch(es) to convert.
.PARAMETER NewName
	Name(s) to assign to the converted virtual switch(es). If blank, keeps the original name.
.PARAMETER UseDefaults
	If specified, uses defaults for all values on the converted switch(es). If not specified, uses the same parameters as the original LBFO+switch or any manually-specified parameters.
.PARAMETER LoadBalancingAlgorithm
	Sets the load balancing algorithm for the converted switch(es). If not specified, uses the same setting as the original LBFO+switch or the default if UseDefaults is set.
.PARAMETER MinimumBandwidthMode
	Sets the desired QoS mode for the converted switch(es). If not specified, uses the same setting as the original LBFO+switch or the default if UseDefaults is set.
	None: No network QoS
	Absolute: minimum bandwidth values specify bits per second
	Weight: minimum bandwidth values range from 1 to 100 and represent percentages
	Default: use system default
	WARNING: Changing the QoS mode may cause guest vNICS to fail to re-attach and may inhibit Live Migration. Use carefully if you have special QoS settings on guest virtual NICs.
.PARAMETER Notes
	A note to associate with the converted switch(es). If not specified, uses the same setting as the original LBFO+switch or the default if UseDefaults is set.
.PARAMETER Force
	If specified, bypasses confirmation.
.NOTES
	Author: Eric Siron
	Version 1.0, December 22, 2019
	Released under MIT license
.EXAMPLE
	ConvertTo-SwitchEmbeddedTeam
	Converts all existing LBFO+switch combinations to switch embedded teams. Copies settings from original switches and management OS virtual NICs to new switch and vNICs.
.EXAMPLE
	ConvertTo-SwitchEmbeddedTeam -Name vSwitch
	Converts the LBFO+switch combination of the virtual switch named "vSwitch" to a switch embedded teams. Copies settings from original switch and management OS virtual NICs to new switch and vNICs.
.EXAMPLE
	ConvertTo-SwitchEmbeddedTeam -Force
	Converts all existing LBFO+team combinations without prompting.
.EXAMPLE
	ConvertTo-SwitchEmbeddedTeam -NewName NewSET
	If the system has one LBFO+switch, converts it to a switch-embedded team with the name "NewSET".
	If the system has multiple LBFO+switch combinations, fails due to mismatch (see next example).
.EXAMPLE
	ConvertTo-SwitchEmbeddedTeam -NewName NewSET1, NewSET2
	If the system has two LBFO+switches, converts them to switch-embedded team with the name "NewSET1" and "NEWSET2", IN THE ORDER THAT GET-VMSWITCH RETRIEVES THEM.
.EXAMPLE
	ConvertTo-SwitchEmbeddedTeam OldSwitch1, OldSwitch2 -NewName NewSET1, NewSET2
	Converts the LBFO+switches named "OldSwitch1" and "OldSwitch2" to SETs named "NewSET1" and "NewSET2", respectively.
.EXAMPLE
	ConvertTo-SwitchEmbeddedTeam -UseDefaults
	Converts all existing LBFO+switch combinations to switch embedded teams. Discards non-default settings for the switch and Hyper-V-related management OS vNICs. Keeps IP addresses and advanced settings (ex. jumbo frames).
.EXAMPLE
	ConvertTo-SwitchEmbeddedTeam -MinimumBandwidthMode Weight
	Converts all existing LBFO+switch combinations to switch embedded teams. Forces the new SET to use "Weight" for its minimum bandwidth mode.
	WARNING: Changing the QoS mode may cause guest vNICS to fail to re-attach and may inhibit Live Migration. Use carefully if you have special QoS settings on guest virtual NICs.
.LINK
https://ejsiron.github.io/Posher-V/ConvertTo-SwitchEmbeddedTeam
#>

#Requires -RunAsAdministrator
#Requires -Module Hyper-V
#Requires -Version 5

[CmdletBinding(DefaultParameterSetName = 'ByName', ConfirmImpact = 'High')]
param(
	[Parameter(Position = 1, ParameterSetName = 'ByName')][String[]]$Name = @(''),
	[Parameter(Position = 1, ParameterSetName = 'ByID', Mandatory = $true)][System.Guid[]]$Id,
	[Parameter(Position = 1, ParameterSetName = 'BySwitchObject', Mandatory = $true)][Microsoft.HyperV.PowerShell.VMSwitch[]]$VMSwitch,
	[Parameter(Position = 2)][String[]]$NewName = @(),
	[Parameter()][Switch]$UseDefaults,
	[Parameter()][Microsoft.HyperV.PowerShell.VMSwitchLoadBalancingAlgorithm]$LoadBalancingAlgorithm,
	[Parameter()][Microsoft.HyperV.PowerShell.VMSwitchBandwidthMode]$MinimumBandwidthMode,
	[Parameter()][String]$Notes = '',
	[Parameter()][Switch]$Force
)

BEGIN
{
	Set-StrictMode -Version Latest
	$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
	$IsClustered = $false
	if(Get-CimInstance -Namespace root -ClassName __NAMESPACE -Filter 'Name="MSCluster"')
	{
		$IsClustered = [bool](Get-CimInstance -Namespace root/MSCluster -ClassName mscluster_cluster -ErrorAction SilentlyContinue)
		$ClusterNode = Get-CimInstance -Namespace root/MSCluster -ClassName MSCluster_Node -Filter ('Name="{0}"' -f $env:COMPUTERNAME)
	}

	function Get-CimAdapterConfigFromVirtualAdapter
	{
		param(
			[Parameter()][psobject]$VNIC
		)
		$VnicCim = Get-CimInstance -Namespace root/virtualization/v2 -ClassName Msvm_InternalEthernetPort -Filter ('Name="{0}"' -f $VNIC.AdapterId)
		$VnicLanEndpoint1 = Get-CimAssociatedInstance -InputObject $VnicCim -ResultClassName Msvm_LANEndpoint
		$NetAdapter = Get-CimInstance -ClassName Win32_NetworkAdapter -Filter ('GUID="{0}"' -f $VnicLANEndpoint1.Name.Substring(($VnicLANEndpoint1.Name.IndexOf('{'))))
		Get-CimAssociatedInstance -InputObject $NetAdapter -ResultClassName Win32_NetworkAdapterConfiguration
	}

	function Get-AdvancedSettingsFromAdapterConfig
	{
		param(
			[Parameter()][psobject]$AdapterConfig
		)
		$MSFTAdapter = Get-CimInstance -Namespace root/StandardCimv2 -ClassName MSFT_NetAdapter -Filter ('InterfaceIndex={0}' -f $AdapterConfig.InterfaceIndex)
		Get-CimAssociatedInstance -InputObject $MSFTAdapter -ResultClassName MSFT_NetAdapterAdvancedPropertySettingData
	}

	class NetAdapterDataPack
	{
		[System.String]$Name
		[System.String]$MacAddress
		[System.Int64]$MinimumBandwidthAbsolute = 0
		[System.Int64]$MinimumBandwidthWeight = 0
		[System.Int64]$MaximumBandwidth = 0
		[System.Int32]$VlanId = 0
		[Microsoft.Management.Infrastructure.CimInstance]$NetAdapterConfiguration
		[Microsoft.Management.Infrastructure.CimInstance[]]$AdvancedProperties
		[Microsoft.Management.Infrastructure.CimInstance[]]$IPAddresses
		[Microsoft.Management.Infrastructure.CimInstance[]]$Gateways

		NetAdapterDataPack([psobject]$VNIC)
		{
			$this.Name = $VNIC.Name
			$this.MacAddress = $VNIC.MacAddress
			if ($VNIC.BandwidthSetting -ne $null)
			{
				$this.MinimumBandwidthAbsolute = $VNIC.BandwidthSetting.MinimumBandwidthAbsolute
				$this.MinimumBandwidthWeight = $VNIC.BandwidthSetting.MinimumBandwidthWeight
				$this.MaximumBandwidth = $VNIC.BandwidthSetting.MaximumBandwidth
			}

			$this.VlanId = [System.Int32](Get-VMNetworkAdapterVlan -VMNetworkAdapter $VNIC).AccessVlanId
			$this.NetAdapterConfiguration = Get-CimAdapterConfigFromVirtualAdapter -VNIC $VNIC
			$this.AdvancedProperties = @(Get-AdvancedSettingsFromAdapterConfig -AdapterConfig $this.NetAdapterConfiguration  | Where-Object -FilterScript { (-not [String]::IsNullOrEmpty($_.DefaultRegistryValue)) -and (-not [String]::IsNullOrEmpty([string]($_.RegistryValue))) -and (-not [String]::IsNullOrEmpty($_.DisplayName)) -and ($_.RegistryValue[0] -ne $_.DefaultRegistryValue) })

			# alternative to the below: use Get-NetIPAddress and Get-NetRoute, but they treat empty results as errors
			$this.IPAddresses = @(Get-CimInstance -Namespace root/StandardCimv2 -ClassName MSFT_NetIPAddress -Filter ('InterfaceIndex={0} AND PrefixOrigin=1' -f $this.NetAdapterConfiguration.InterfaceIndex))
			$this.Gateways = @(Get-CimInstance -Namespace root/StandardCimv2 -ClassName MSFT_NetRoute -Filter ('InterfaceIndex={0} AND Protocol=3' -f $this.NetAdapterConfiguration.InterfaceIndex)) # documentation says Protocol=2 for NetMgmt, testing shows otherwise
		}
	}

	class SwitchDataPack
	{
		[System.String]$Name
		[Microsoft.HyperV.PowerShell.VMSwitchBandwidthMode]$BandwidthReservationMode
		[System.UInt64]$DefaultFlow
		[System.String]$TeamName
		[System.String[]]$TeamMembers
		[System.UInt32]$LoadBalancingAlgorithm
		[NetAdapterDataPack[]]$HostVNICs

		SwitchDataPack(
			[psobject]$VSwitch,
			[Microsoft.Management.Infrastructure.CimInstance]$Team,
			[System.Object[]]$VNICs
		)
		{
			$this.Name = $VSwitch.Name
			$this.BandwidthReservationMode = $VSwitch.BandwidthReservationMode
			switch ($this.BandwidthReservationMode)
			{
				[Microsoft.HyperV.PowerShell.VMSwitchBandwidthMode]::Absolute { $this.DefaultFlow = $VSwitch.DefaultFlowMinimumBandwidthAbsolute }
				[Microsoft.HyperV.PowerShell.VMSwitchBandwidthMode]::Weight { $this.DefaultFlow = $VSwitch.DefaultFlowMinimumBandwidthWeight }
				default { $this.DefaultFlow = 0 }
			}
			$this.TeamName = $Team.Name
			$this.TeamMembers = ((Get-CimAssociatedInstance -InputObject $Team -ResultClassName MSFT_NetLbfoTeamMember).Name)
			$this.LoadBalancingAlgorithm = $Team.LoadBalancingAlgorithm
			$this.HostVNICs = $VNICs
		}
	}

	function Set-CimAdapterProperty
	{
		param(
			[Parameter()][System.Object]$InputObject,
			[Parameter()][System.String]$MethodName,
			[Parameter()][System.Object]$Arguments,
			[Parameter()][System.String]$Activity,
			[Parameter()][System.String]$Url
		)

		Write-Verbose -Message $Activity
		$CimResult = Invoke-CimMethod -InputObject $InputObject -MethodName $MethodName -Arguments $Arguments -ErrorAction Continue

		if ($CimResult -and $CimResult.ReturnValue -gt 0 )
		{
			Write-Warning -Message ('CIM error from operation: {0}. Consult {1} for error code {2}' -f $Activity, $Url, $CimResult.ReturnValue) -WarningAction Continue
		}
	}
}

PROCESS
{
	$VMSwitches = New-Object System.Collections.ArrayList
	$SwitchRebuildData = New-Object System.Collections.ArrayList

	switch ($PSCmdlet.ParameterSetName)
	{
		'ByID'
		{
			$VMSwitches.AddRange($Id.ForEach( { Get-VMSwitch -Id $_ -ErrorAction SilentlyContinue }))
		}
		'BySwitchObject'
		{
			$VMSwitches.AddRange($VMSwitch.ForEach( { $_ }))
		}
		default	# ByName
		{
			$NameList = New-Object System.Collections.ArrayList
			$NameList.AddRange($Name.ForEach( { $_.Trim() }))
			if ($NameList.Contains('') -or $NameList.Contains('*'))
			{
				$VMSwitches.AddRange(@(Get-VMSwitch -ErrorAction SilentlyContinue))
			}
			else
			{
				$VMSwitches.AddRange($NameList.ForEach( { Get-VMSwitch -Name $_ -ErrorAction SilentlyContinue }))
			}
		}
	}
	if ($VMSwitches.Count)
	{
		$VMSwitches = @(Select-Object -InputObject $VMSwitches -Unique)
	}
	else
	{
		throw('No virtual switches match the provided criteria')
	}

	Write-Progress -Activity 'Pre-flight' -Status 'Verifying operating system version' -PercentComplete 5 -Id 1
	Write-Verbose -Message 'Verifying operating system version'
	$OSVersion = [System.Version]::Parse((Get-CimInstance -ClassName Win32_OperatingSystem).Version)
	if ($OSVersion.Major -lt 10)
	{
		throw('Switch-embedded teams not supported on host operating system versions before 2016')
	}

	Write-Progress -Activity 'Pre-flight' -Status 'Loading virtual VMswitches' -PercentComplete 15 -Id 1

	if ($NewName.Count -gt 0 -and $NewName.Count -ne $VMSwitches.Count)
	{
		$SwitchNameMismatchMessage = 'Switch count ({0}) does not match NewName count ({1}).' -f $VMSwitches.Count, $NewName.Count
		if ($NewName.Count -lt $VMSwitches.Count)
		{
			$SwitchNameMismatchMessage += ' If you wish to rename some VMswitches but not others, specify an empty string for the VMswitches to leave.'
		}
		throw($SwitchNameMismatchMessage)
	}

	Write-Progress -Activity 'Pre-flight' -Status 'Validating virtual switch configurations' -PercentComplete 25 -Id 1
	Write-Verbose -Message 'Validating virtual switches'
	foreach ($VSwitch in $VMSwitches)
	{
		try
		{
			Write-Progress -Activity ('Validating virtual switch "{0}"' -f $VSwitch.Name) -Status 'Switch is external' -PercentComplete 25 -ParentId 1
			Write-Verbose -Message ('Verifying that switch "{0}" is external' -f $VSwitch.Name)
			if ($VSwitch.SwitchType -ne [Microsoft.HyperV.PowerShell.VMSwitchType]::External)
			{
				Write-Warning -Message ('Switch "{0}" is not external, skipping' -f $VSwitch.Name)
				continue
			}

			Write-Progress -Activity ('Validating virtual switch "{0}"' -f $VSwitch.Name) -Status 'Switch is not a SET' -PercentComplete 50 -ParentId 1
			Write-Verbose -Message ('Verifying that switch "{0}" is not already a SET' -f $VSwitch.Name)
			if ($VSwitch.EmbeddedTeamingEnabled)
			{
				Write-Warning -Message ('Switch "{0}" already uses SET, skipping' -f $VSwitch.Name)
				continue
			}

			Write-Progress -Activity ('Validating virtual switch "{0}"' -f $VSwitch.Name) -Status 'Switch uses LBFO' -PercentComplete 75 -ParentId 1
			Write-Verbose -Message ('Verifying that switch "{0}" uses an LBFO team' -f $VSwitch.Name)
			$TeamAdapter = Get-CimInstance -Namespace root/StandardCimv2 -ClassName MSFT_NetLbfoTeamNic -Filter ('InterfaceDescription="{0}"' -f $VSwitch.NetAdapterInterfaceDescription)
			if ($TeamAdapter -eq $null)
			{
				Write-Warning -Message ('Switch "{0}" does not use a team, skipping' -f $VSwitch.Name)
				continue
			}
			if ($TeamAdapter.VlanID)
			{
				Write-Warning -Message ('Switch "{0}" is bound to a team NIC with a VLAN assignment, skipping' -f $VSwitch.Name)
				continue
			}
		}
		catch
		{
			Write-Warning -Message ('Switch "{0}" failed validation, skipping. Error: {1}' -f $VSwitch.Name, $_.Exception.Message)
			continue
		}
		finally
		{
			Write-Progress -Activity ('Validating virtual switch "{0}"' -f $VSwitch.Name) -Completed -ParentId 1
		}

		Write-Progress -Activity ('Loading information from virtual switch "{0}"' -f $VSwitch.Name) -Status 'Team NIC' -PercentComplete 25 -ParentId 1
		Write-Verbose -Message 'Loading team'
		$Team = Get-CimAssociatedInstance -InputObject $TeamAdapter -ResultClassName MSFT_NetLbfoTeam


		Write-Progress -Activity ('Loading information from virtual switch "{0}"' -f $VSwitch.Name) -Status 'Host virtual adapters' -PercentComplete 50 -ParentId 1
		Write-Verbose -Message 'Loading management adapters connected to this switch'
		$HostVNICs = Get-VMNetworkAdapter -ManagementOS -SwitchName $VSwitch.Name

		Write-Verbose -Message 'Compiling virtual switch and management OS virtual NIC information'
		Write-Progress -Activity ('Loading information from virtual switch "{0}"' -f $VSwitch.Name) -Status 'Storing vSwitch data' -PercentComplete 75 -ParentId 1
		$OutNull = $SwitchRebuildData.Add([SwitchDataPack]::new($VSwitch, $Team, ($HostVNICs.ForEach({ [NetAdapterDataPack]::new($_) }))))
		Write-Progress -Activity ('Loading information from virtual switch "{0}"' -f $VSwitch.Name) -Completed
	}
	Write-Progress -Activity 'Pre-flight' -Status 'Cleaning up' -PercentComplete 99 -ParentId 1

	Write-Verbose -Message 'Clearing loop variables'
	$VSwitch = $Team = $TeamAdapter = $HostVNICs = $null

	Write-Progress -Activity 'Pre-flight' -Completed

	if($SwitchRebuildData.Count -eq 0)
	{
		Write-Warning -Message 'No eligible virtual switches found.'
		exit 1
	}

	$SwitchMark = 0
	$SwitchCounter = 1
	$SwitchStep = 1 / $SwitchRebuildData.Count * 100
	$ClusterNodeRunning = $IsClustered

	foreach ($OldSwitchData in $SwitchRebuildData)
	{
		$SwitchName = $OldSwitchData.Name
		if($NewName.Count -gt 0)
		{
			$SwitchName = $NewName[($SwitchCounter - 1)]
		}
		Write-Progress -Activity 'Rebuilding switches' -Status ('Processing virtual switch {0} ({1}/{2})' -f $SwitchName, $SwitchCounter, $SwitchRebuildData.Count) -PercentComplete $SwitchMark -Id 1
		$SwitchCounter++
		$SwitchMark += $SwitchStep
		$ShouldProcessTargetText = 'Virtual switch {0}' -f $OldSwitchData.Name
		$ShouldProcessOperation = 'Disconnect all virtual adapters, remove team and switch, build switch-embedded team, replace management OS vNICs, reconnect virtual adapters'
		if ($Force -or $PSCmdlet.ShouldProcess($ShouldProcessTargetText , $ShouldProcessOperation))
		{
			if($ClusterNodeRunning)
			{
				Write-Verbose -Message 'Draining cluster node'
				Write-Progress -Activity 'Draining cluster node' -Status 'Draining'
				$OutNull = Invoke-CimMethod -InputObject $ClusterNode -MethodName 'Pause' -Arguments @{DrainType=2;TargetNode=''}
				while($ClusterNodeRunning)
				{
					Start-Sleep -Seconds 1
					$ClusterNode = Get-CimInstance -InputObject $ClusterNode
					switch($ClusterNode.NodeDrainStatus)
					{
						0 { Write-Error -Message 'Failed to initiate cluster node drain' }
						2 { $ClusterNodeRunning = $false }
						3 { Write-Error -Message 'Failed to drain cluster roles' }
						# 1 is all that's left, will cause loop to continue
					}
				}
			}
			Write-Progress -Activity 'Draining cluster node' -Completed

			$SwitchProgressParams = @{Activity = ('Processing switch {0}' -f $OldSwitchData.Name); ParentId = 1; Id=2 }
			Write-Verbose -Message 'Disconnecting virtual machine adapters'
			Write-Progress @SwitchProgressParams -Status 'Disconnecting virtual machine adapters' -PercentComplete 10
			Write-Verbose -Message 'Loading VM adapters connected to this switch'
			$GuestVNICs = Get-VMNetworkAdapter -VMName * | Where-Object -Property SwitchName -EQ $OldSwitchData.Name
			if($GuestVNICs)
			{
				Disconnect-VMNetworkAdapter -VMNetworkAdapter $GuestVNICs
			}

			Start-Sleep -Milliseconds 250	# seems to prefer a bit of rest time between removal commands

			if($OldSwitchData.HostVNICs)
			{
				Write-Verbose -Message 'Removing management vNICs'
				Write-Progress @SwitchProgressParams -Status 'Removing management vNICs' -PercentComplete 20
				Remove-VMNetworkAdapter -ManagementOS
			}

			Start-Sleep -Milliseconds 250	# seems to prefer a bit of rest time between removal commands

			Write-Verbose -Message 'Removing virtual switch'
			Write-Progress @SwitchProgressParams -Status 'Removing virtual switch' -PercentComplete 30
			Remove-VMSwitch -Name $OldSwitchData.Name -Force

			Start-Sleep -Milliseconds 250	# seems to prefer a bit of rest time between removal commands

			Write-Verbose -Message 'Removing team'
			Write-Progress @SwitchProgressParams -Status 'Removing team' -PercentComplete 40
			Remove-NetLbfoTeam -Name $OldSwitchData.TeamName -Confirm:$false

			Start-Sleep -Milliseconds 250	# seems to prefer a bit of rest time between removal commands

			Write-Verbose -Message 'Creating SET'
			Write-Progress @SwitchProgressParams -Status 'Creating SET' -PercentComplete 50
			$SetLoadBalancingAlgorithm = $null
			if (-not $UseDefaults)
			{
				if ($OldSwitchData.LoadBalancingAlgorithm -eq 5)
				{
					$SetLoadBalancingAlgorithm = [Microsoft.HyperV.PowerShell.VMSwitchLoadBalancingAlgorithm]::Dynamic # 5 is dynamic; https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ndisimplatcimprov/msft-netlbfoteam
				}
				else # SET does not have LBFO's hash options for load-balancing; assume that the original switch used a non-Dynamic mode for a reason
				{
					$SetLoadBalancingAlgorithm = [Microsoft.HyperV.PowerShell.VMSwitchLoadBalancingAlgorithm]::HyperVPort
				}
			}
			if ($LoadBalancingAlgorithm)
			{
				$SetLoadBalancingAlgorithm = $LoadBalancingAlgorithm
			}

			$NewMinimumBandwidthMode = $null
			if(-not $UseDefaults)
			{
				$NewMinimumBandwidthMode = $OldSwitchData.BandwidthReservationMode
			}
			if ($MinimumBandwidthMode)
			{
				$NewMinimumBandwidthMode = $MinimumBandwidthMode
			}
			$NewSwitchParams = @{NetAdapterName=$OldSwitchData.TeamMembers}
			if($NewMinimumBandwidthMode)
			{
				$NewSwitchParams.Add('MinimumBandwidthMode', $NewMinimumBandwidthMode)
			}

			try
			{
				$NewSwitch = New-VMSwitch @NewSwitchParams -Name $SwitchName -AllowManagementOS $false -EnableEmbeddedTeaming $true -Notes $Notes
			}
			catch
			{
				Write-Error -Message ('Unable to create virtual switch {0}: {1}' -f $SwitchName, $_.Exception.Message) -ErrorAction Continue
				continue
			}

			if($SetLoadBalancingAlgorithm)
			{
				Write-Verbose -Message ('Setting load balancing mode to {0}' -f $SetLoadBalancingAlgorithm)
				Write-Progress @SwitchProgressParams -Status 'Setting SET load balancing algorithm' -PercentComplete 60
				Set-VMSwitchTeam -Name $NewSwitch.Name -LoadBalancingAlgorithm $SetLoadBalancingAlgorithm
			}

			$VNICCounter = 0

			foreach($VNIC in $OldSwitchData.HostVNICs)
			{
				$VNICCounter++
				Write-Progress @SwitchProgressParams -Status ('Configuring management OS vNIC {0}/{1}' -f $VNICCounter, $OldSwitchData.HostVNICs.Count) -PercentComplete 70

				$VNICProgressParams = @{Activity = ('Processing VNIC {0}' -f $VNIC.Name); ParentId = 2; Id=3 }

				Write-Verbose -Message ('Adding virtual adapter "{0}" to switch "{1}"' -f $VNIC.Name, $NewSwitch.Name)
				Write-Progress @VNICProgressParams -Status 'Adding vNIC' -PercentComplete 10
				$NewNic = Add-VMNetworkAdapter -SwitchName $NewSwitch.Name -ManagementOS -Name $VNIC.Name -StaticMacAddress $VNIC.MacAddress -Passthru
				$SetNicParams = @{ }
				if ((-not $UseDefaults) -and $VNIC.MinimumBandwidthAbsolute -and $NewSwitch.BandwidthReservationMode -eq [Microsoft.HyperV.PowerShell.VMSwitchBandwidthMode]::Absolute)
				{
					$SetNicParams.Add('MinimumBandwidthAbsolute', $VNIC.MinimumBandwidthAbsolute)
				}
				elseif ((-not $UseDefaults) -and $VNIC.MinimumBandwidthWeight -and $NewSwitch.BandwidthReservationMode -eq [Microsoft.HyperV.PowerShell.VMSwitchBandwidthMode]::Weight)
				{
					$SetNicParams.Add('MinimumBandwidthWeight', $VNIC.MinimumBandwidthWeight)
				}
				if ($VNIC.MaximumBandwidth)
				{
					$SetNicParams.Add('MaximumBandwidth', $VNIC.MaximumBandwidth)
				}
				Write-Verbose -Message ('Setting properties on virtual adapter "{0}" on switch "{1}"' -f $VNIC.Name, $NewSwitch.Name)

				Write-Progress @VNICProgressParams -Status 'Setting vNIC parameters' -PercentComplete 20
				Set-VMNetworkAdapter -VMNetworkAdapter $NewNic @SetNicParams -ErrorAction Continue
				if($VNIC.VlanId)
				{
					Write-Progress @VNICProgressParams -Status 'Setting VLAN ID' -PercentComplete 30
					Write-Verbose -Message ('Setting VLAN ID on virtual adapter "{0}" on switch "{1}"' -f $VNIC.Name, $NewSwitch.Name)
					Set-VMNetworkAdapterVlan -VMNetworkAdapter $NewNic -Access -VlanId $VNIC.VlanId
				}
				$NewNicConfig = Get-CimAdapterConfigFromVirtualAdapter -VNIC $NewNic

				if ($VNIC.IPAddresses.Count -gt 0)
				{
					Write-Progress @VNICProgressParams -Status 'Setting IP and subnet masks' -PercentComplete 40
					foreach($IPAddressData in $VNIC.IPAddresses)
					{
						Write-Verbose -Message ('Setting IP address {0}' -f $IPAddressData.IPAddress)
						$OutNull = New-NetIPAddress -InterfaceIndex $NewNicConfig.InterfaceIndex -IPAddress $IPAddressData.IPAddress -PrefixLength $IPAddressData.PrefixLength -SkipAsSource $IPAddressData.SkipAsSource -ErrorAction Continue
					}

					Write-Progress @VNICProgressParams -Status 'Setting DNS registration behavior' -PercentComplete 41
					Set-CimAdapterProperty -InputObject $NewNicConfig -MethodName 'SetDynamicDNSRegistration' `
					-Arguments @{ FullDNSRegistrationEnabled = $VNIC.NetAdapterConfiguration.FullDNSRegistrationEnabled; DomainDNSRegistrationEnabled = $VNIC.NetAdapterConfiguration.DomainDNSRegistrationEnabled } `
					-Activity ('Setting DNS registration behavior (dynamic registration: {0}, with domain name: {1}) on {2}' -f $VNIC.NetAdapterConfiguration.FullDNSRegistrationEnabled, $VNIC.NetAdapterConfiguration.DomainDNSRegistrationEnabled, $NewNic.Name) `
					-Url 'https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/setdynamicdnsregistration-method-in-class-win32-networkadapterconfiguration'

					foreach($GatewayData in $VNIC.Gateways)
					{
						Write-Verbose -Message ('Setting gateway address {0}' -f $GatewayData.NextHop)
						$OutNull = New-NetRoute -InterfaceIndex $NewNicConfig.InterfaceIndex -DestinationPrefix $GatewayData.DestinationPrefix -NextHop $GatewayData.NextHop -RouteMetric $GatewayData.RouteMetric
					}

					Write-Progress @VNICProgressParams -Status 'Setting gateways' -PercentComplete 42
					if ($VNIC.NetAdapterConfiguration.DefaultIPGateway)
					{
						Set-CimAdapterProperty -InputObject $NewNicConfig -MethodName 'SetGateways' `
						-Arguments @{ DefaultIPGateway = $VNIC.NetAdapterConfiguration.DefaultIPGateway } `
						-Activity ('Setting gateways {0} on {1}'  -f $VNIC.NetAdapterConfiguration.DefaultIPGateway, $NewNic.Name) `
						-Url 'https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/setgateways-method-in-class-win32-networkadapterconfiguration'
					}

					if($VNIC.NetAdapterConfiguration.DNSDomain)
					{
						Write-Progress @VNICProgressParams -Status 'Setting DNS domain' -PercentComplete 43
						Set-CimAdapterProperty -InputObject $NewNicConfig -MethodName 'SetDNSDomain' `
						-Arguments @{ DNSDomain = $VNIC.NetAdapterConfiguration.DNSDomain } `
						-Activity ('Setting DNS domain {0} on {1}' -f $VNIC.NetAdapterConfiguration.DNSDomain, $NewNic.Name) `
						-Url 'https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/setdnsdomain-method-in-class-win32-networkadapterconfiguration'
					}

					if ($VNIC.NetAdapterConfiguration.DNSServerSearchOrder)
					{
						Write-Progress @VNICProgressParams -Status 'Setting DNS servers' -PercentComplete 44
						Set-CimAdapterProperty -InputObject $NewNicConfig -MethodName 'SetDNSServerSearchOrder' `
						-Arguments @{ DNSServerSearchOrder = $VNIC.NetAdapterConfiguration.DNSServerSearchOrder } `
						-Activity ('setting DNS servers {0} on {1}' -f [String]::Join(', ', $VNIC.NetAdapterConfiguration.DNSServerSearchOrder), $NewNic.Name) `
						-Url 'https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/setdnsserversearchorder-method-in-class-win32-networkadapterconfiguration'
					}

					if($VNIC.NetAdapterConfiguration.WINSPrimaryServer)
					{
						Write-Progress @VNICProgressParams -Status 'Setting WINS servers' -PercentComplete 45
						Set-CimAdapterProperty -InputObject $NewNicConfig -MethodName 'SetWINSServer' `
						-Arguments @{ WINSPrimaryServer = $VNIC.NetAdapterConfiguration.WINSPrimaryServer; WINSSecondaryServer = $VNIC.NetAdapterConfiguration.WINSSecondaryServer }
						-Activity ('Setting WINS servers {0} on {1}' -f ([String]::Join(', ', $VNIC.NetAdapterConfiguration.WINSPrimaryServer, $VNIC.NetAdapterConfiguration.WINSSecondaryServer)), $NewNic.Name) `
						-Url 'https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/setwinsserver-method-in-class-win32-networkadapterconfiguration'
					}
				}
				if($VNIC.NetAdapterConfiguration.TcpipNetbiosOptions)	# defaults to 0
				{
					Write-Progress @VNICProgressParams -Status 'Setting NetBIOS over TCP/IP behavior' -PercentComplete 50
					Set-CimAdapterProperty -InputObject $NewNicConfig -MethodName 'SetTcpipNetbios' `
					-Arguments @{ TcpipNetbiosOptions = $VNIC.NetAdapterConfiguration.TcpipNetbiosOptions } `
					-Activity ('Setting NetBIOS over TCP/IP behavior on {0} to {1}' -f $NewNic.Name, $VNIC.NetAdapterConfiguration.TcpipNetbiosOptions) `
					-Url 'https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/settcpipnetbios-method-in-class-win32-networkadapterconfiguration'
				}

				Write-Progress @VNICProgressParams -Status 'Applying advanced properties' -PercentComplete 60
				$NewNicAdvancedProperties = Get-AdvancedSettingsFromAdapterConfig -AdapterConfig $NewNicConfig
				$PropertiesCounter = 0
				$PropertyProgressParams = @{Activity = 'Processing VNIC advanced properties'; ParentId = 3; Id=4 }
				foreach($SourceAdvancedProperty in $VNIC.AdvancedProperties)
				{
					foreach($NewNicAdvancedProperty in $NewNicAdvancedProperties)
					{
						if($SourceAdvancedProperty.ElementName -eq $NewNicAdvancedProperty.ElementName)
						{
							$PropertiesCounter++
							Write-Progress @PropertyProgressParams -PercentComplete ($PropertiesCounter / $VNIC.AdvancedProperties.Count * 100) -Status ('Applying property {0}' -f $SourceAdvancedProperty.DisplayName)
							Write-Verbose ('Setting advanced property {0} to {1} on {2}' -f $SourceAdvancedProperty.DisplayName, $SourceAdvancedProperty.DisplayValue, $VNIC.Name)
							$NewNicAdvancedProperty.RegistryValue = $SourceAdvancedProperty.RegistryValue
							Set-CimInstance -InputObject $NewNicAdvancedProperty -ErrorAction Continue
						}
					}
				}
			}

			Write-Progress @VNICProgressParams -Completed

			Write-Progress @SwitchProgressParams -Status 'Reconnecting guest vNICs' -PercentComplete 80

			if($GuestVNICs)
			{
				foreach ($GuestVNIC in $GuestVNICs)
				{
					try
					{
						Connect-VMNetworkAdapter -VMNetworkAdapter $GuestVNIC -VMSwitch $NewSwitch
					}
					catch
					{
						Write-Error -Message ('Failed to connect virtual adapter "{0}" with MAC address "{1}" to virtual switch "{2}": {3}' -f $GuestVNIC.Name, $GuestVNIC.MacAddress, $NewSwitch.Name, $_.Exception.Message) -ErrorAction Continue
					}
				}
			}

			Write-Progress @SwitchProgressParams -Completed
		}
	}
	if($IsClustered)
	{
		Write-Verbose -Message 'Resuming cluster node'
		$OutNull = Invoke-CimMethod -InputObject $ClusterNode -MethodName 'Resume' -Arguments @{FailbackType=1}
	}
}

The post Free Script – Convert Legacy Teamed Hyper-V vSwitch to SET appeared first on Altaro DOJO | Hyper-V.

]]>
https://www.altaro.com/hyper-v/free-script-vswitch-set/feed/ 3
How to Customize Hyper-V VMs using PowerShell https://www.altaro.com/hyper-v/customize-vm-powershell/ https://www.altaro.com/hyper-v/customize-vm-powershell/#respond Fri, 03 Jan 2020 17:25:47 +0000 https://www.altaro.com/hyper-v/?p=18251 A comprehensive guide to customizing Hyper-V virtual machines using PowerShell. Covers best practices for settings, automation, optimization, and more!

The post How to Customize Hyper-V VMs using PowerShell appeared first on Altaro DOJO | Hyper-V.

]]>

In this article, we’ll be covering all the main points for deploying and modifying virtual machines in Hyper-V using PowerShell.

You can create a Hyper-V virtual machine easily using a number of tools. The “easy” tool, Hyper-V Manager’s New Virtual Machine Wizard (and its near-equivalent in Failover Cluster Manager), creates only a basic system. It has a number of defaults that you might not like. If you forget to change something, then you might have to schedule downtime later to correct it. You have other choices for VM creation. Among these, PowerShell gives you the greatest granularity and level of control. We’ll take a tour of the capability at your fingertips. After the tour, we will present some ways that you can leverage this knowledge to make VM creation simpler, quicker, and less error-prone than any of the GUI methods.

Cmdlets Related to Virtual Machine Creation

Of course, you create virtual machines using New-VM. But, like the wizards, it has limits. You will use other cmdlets to finesse the final product into exactly what you need.

The above cmdlets encompass the features needed for a majority of cases. If you need something else, you can start with the complete list of Hyper-V-related cmdlets.

Note: This article was written using the cmdlets as found on Windows Server 2019.

Comparing PowerShell to the Wizards for Virtual Machine Creation

PowerShell makes some things a lot quicker than the GUI tools. That doesn’t always apply to virtual machine creation. You will need to consider the overall level of effort before choosing either approach. Start with an understanding of what each approach asks of you.

The GUI Wizard Outcome

Virtual machine configuration points you can control when creating a virtual machine using the wizard:

  • Name
  • Virtual machine generation
  • Storage location
  • Virtual switch connection
  • The startup memory quantity and whether the VM uses Dynamic Memory
  • Attach or create one VHD or VHDX to the default location
  • Initial boot configuration

If you used the wizard from within Failover Cluster Manager, it will have also added the VM to the cluster’s highly-available roles.

The wizard does fairly well at hitting the major configuration points for a new virtual machine. It does miss some things, though. Most notably, you only get vCPU. Once you finish using the wizard to create a virtual machine, you must then work through the VM’s properties to fix up anything else.

The Windows Admin Center Outcome

Virtual machine configuration points you can control when creating a virtual machine using Windows Admin Center:

  • Name
  • Virtual machine generation
  • The system that will host the VM (if you started VM creation from the cluster panel)
  • Base storage location — one override for both the VM’s configuration files and the VHDX(s)
  • Virtual switch connection
  • The number of virtual processors
  • The startup memory quantity, whether the VM uses Dynamic Memory, and DM’s minimum and maximum values
  • Attach or create one or more VHDs or VHDXs
  • Initial boot configuration

If you used the wizard from within Failover Cluster Manager, it will have also added the VM to the cluster’s highly-available roles.

Windows Admin Center does more than the MMC wizards, making it more likely that you can immediately use the created virtual machine. It does miss a few common configuration points, such as VLAN assignment and startup/shutdown settings.

The PowerShell Outcome

As for PowerShell, we have nothing missed on the outcome. It can do everything. Some parts take a bit more effort. You will often need two or more cmdlets to fully create a VM as desired. Before we demonstrate them, we need to cover the difference between PowerShell and the above GUI methods.

Why Should I Use PowerShell Instead of the GUI to Create a Virtual Machine?

So, if PowerShell takes more work, why would you do it? Well, if you have to create only one or two VMs, maybe you wouldn’t. In a lot of cases, it makes the most sense:

  • One-stop location for the creation of VMs with advanced settings
  • Repeatable VM creation steps
  • More precise control over the creation
  • Access to features and settings unavailable in the GUI

For single VM creation, PowerShell saves you from some double-work and usage of multiple interfaces. You don’t have to run a wizard to create the VM and then dig through property sheets to make changes. You also don’t have to start in a wizard and then switch to PowerShell if you want to change a setting not included in the GUI.

Understanding Permissions for Hyper-V’s Cmdlets

If you run these cmdlets locally on the Hyper-V host as presented in this article, then you must belong to the local Administrators group. I have personally never used the “Hyper-V Administrators” group, ever, just on principle. A Hyper-V host should not do anything else, and I have not personally encountered a situation where it made sense to separate host administration from Hyper-V administration. I have heard from others that membership in the “Hyper-V Administrators” group does not grant the powers that they expect. Your mileage may vary.

Additional Requirements for Remote Storage

If the storage location for your VMs or virtual hard disks resides on a remote system (SMB), then you have additional concerns that require you to understand the security model of Hyper-V’s helper services. Everything that you do with the Hyper-V cmdlets (and GUI tools) accesses a central CIM-based API. These APIs do their work by a two-step process:

  • The Hyper-V host verifies that your account has permission to access the requested API
  • Service on the Hyper-V host carries out the requested action within its security context

By default, these services run as the “Local System” account. They present themselves to other entities on the network as the Hyper-V host’s computer account, not your account. Changing the account that runs the services places you in an unsupported configuration. Just understand that they run under that account and act accordingly.

The Hyper-V host’s computer account must have at least Modify the permission on the remote NTFS/ReFS file system and at least Change on the SMB share.

Additional Requirements for Remote Sessions

If you run these cmdlets remotely, whether explicitly (inside a PSSession) or implicitly (using the ComputerName) parameter, and you do anything that depends on SMB storage (a second hop), then you must configure delegation.

The security points of a delegated operation:

  • The account that you use to run the cmdlet must have administrator privileges on the Hyper-V host
  • The Hyper-V host must allow delegation of credentials to the target location
  • You must configure the target SMB share as indicated in the last sentence of the preceding section

These rules apply whether you allow the commands to use the host’s configured default locations or if you override.

If you need help with the delegation part, we have a script for delegation.

Shortest Possible Use of New-VM

You can run New-VM with only one required parameter:

New-VM -Name 'demovm'

This creates a virtual machine on the local host with the following characteristics:

  • Name: “demovm”
  • Generation 1
  • 1 vCPU
  • No virtual hard disk
  • Virtual CD/DVD attached to virtual IDE controller 1, location 0
  • Synthetic adapter, not connected to a virtual switch
  • Boots from CD (only bootable device on such a system)

I do not use the cmdlet this way, ever. I personally create only Generation 2 machines now unless I have some overriding reason. You can change all other options after creation. Also, I tend to connect to the virtual switch right away, as it saves me a cmdlet later.

I showed this usage so that you understand the default behavior.

Simple VM Creation in PowerShell

We’ll start with a very basic VM, using simple but verbose cmdlets.

New-VM -Name 'demovm' -MemoryStartupBytes 2gb -Generation 2 -SwitchName 'vSwitch'

The above creates a new Generation 2 virtual machine in the host’s default location named “demovm” with 2 gigabytes of startup memory and connects it to the virtual switch named “vSwitch”. It uses static memory because New-VM cannot enable Dynamic Memory. It uses one vCPU because New-VM cannot override that. It does not have a VHDX. We can do that with New-VM, but I have a couple of things to note for that, and I wanted to start easy. Yes, you will have to issue more cmdlets to change the additional items, but you’re already in the right place to do that. No need to switch to another screen.

Before we move on to post-creation modifications, let’s look at uncommon creation options.

Create a Simple VM with a Specific Version in PowerShell

New-VM has one feature that the GUI cannot replicate by any means: it can create a VM with a specific configuration version. Without overriding, you can only create VMs that use the maximum supported version of the host that builds the VM. If you will need to migrate or replicate the VM to a host running an older version, then you must use New-VM and specify a version old enough to run on all necessary hosts.

To create the same diskless VM as above, but that can run on a Windows Server 2012 R2 host:

New-VM -Name 'demovm' -MemoryStartupBytes 2gb -Generation 2 -SwitchName 'vSwitch' -Version 5.0

You can see the possible versions and their compatibility levels with Get-VMHostSupportedVersion.

Be aware that creating a VM with a lower version may have unintended side effects. For instance, versions prior to 8 don’t support hardware thread counts so they won’t have access to Hyper-Threading when running on a Hyper-V host that uses the core scheduler. You can see the official matrix of VM features by version on the related Microsoft docs page.

Note: New-VM also exposes the Experimental and Prerelease switches, but these don’t work on regular release versions of Hyper-V. These switches create VMs with versions above the host’s normally supported maximum. Perhaps they function on Insider versions, but I have not tried.

Simple VM Creation with Positional Parameters

When we write scripts, we should always type out the full names of parameters. But, if you’re working interactively, take all the shortcuts you like. Let’s make that same “demovm”, but save ourselves a few keystrokes:

new-vm 'demovm' 2gb 2 -SwitchName 'vSwitch'

SwitchName is the only non-positional parameter that we used. You can tell from the help listing (Get-Help New-VM):

New-VM [[-Name] <String>] [[-MemoryStartupBytes] <Int64>] [[-Generation] {1 | 2}] [-AsJob] [-BootDevice {Floppy | CD | IDE | LegacyNetworkAdapter | NetworkAdapter | VHD}] [-CimSession <CimSession[]>] [-ComputerName <String[]>] [-Confirm] [-Credential <PSCredential[]>] [-Experimental] [-Force] -NewVHDPath <String> -NewVHDSizeBytes <UInt64> [-Path <String>] [-Prerelease] [-SwitchName <String>] [-Version <Version>] [-WhatIf] [<CommonParameters>]

Each parameter surrounded by double brackets ([[ParameterName]]) is positional. As long as you supply its value in the exact order that it appears, you do not need to type its name.

In only 43 characters, we have accomplished the same as all but one of the wizard’s tabs. If you want to make it even shorter, the quote marks around the VM and switch names are only necessary if they contain spaces. And, once the cmdlet completes, we can just keep typing to change anything that New-VM didn’t cover.

Create a Simple VM in a Non-Default Path

We can place the VM in a location other than the default with one simple change, but it has a behavioral side effect. First, the cmdlet:

New-VM -Name 'demovm' -MemoryStartupBytes 2gb -Generation 2 -SwitchName 'vSwitch' -Path 'C:LocalVMs'

The Path parameter overrides the placement of the VM from the host defaults. It does not impact the placement of any virtual hard disks.

As for the previously mentioned side effect, compare the value of the Path parameter of a VM created using the default (on my system):

Path                                : \svstore01vms

The Path parameter of a VM with the overriden path value:

Path                                : C:LocalVMsdemovm

When you do not specify a Path, the cmdlet will place all of the virtual machine’s files in the relevant subfolders of the host’s default path (Virtual Machines, Snapshots, etc.). When you specify a path, it first creates a subfolder with the name of the VM, then creates all those other subfolders inside. As far as I know, all of the tools exhibit this same behavior (I did not test WAC).

Create a VM with a VHDX, Single Cmdlet

To create the VM with a virtual hard disk in one shot, you must specify both the NewVHDPath and NewVHDSizeBytes parameter. NewVHDPath operates somewhat independently of Path.

Start with the easiest usage:

New-VM -Name 'demovm' -MemoryStartupBytes 2gb -Generation 2 -SwitchName 'vSwitch' -NewVHDPath 'demovm.vhdx' -NewVHDSizeBytes 60gb

The above cmdlet does very nearly all the same things like the GUI wizard but in one line. It starts by doing the same things as the first simple cmdlet that I showed you. Then  It also creates a VHDX of the specified name. Since the NewVHDPath parameter only indicates the file name, the cmdlet creates it in the host’s default virtual hard disk storage location. To finish up, it attaches the new disk to the VM’s first disk boot location (IDE 0:0 or SCSI 0:0, depending on VM Generation).

Create a VM with a VHDX, Override the VHDX Storage Location

Don’t want the VHDX in the default location? Just change NewVHDPath so that it specifies the full path to the desired location:

New-VM -Name 'demovm' -MemoryStartupBytes 2gb -Generation 2 -SwitchName 'vSwitch' -NewVHDPath '\svstore02vmstoreVirtual Hard Disksdemovm.vhdx' -NewVHDSizeBytes 60gb

Create a VM with a VHDX, Override the Entire VM Location

Want to change the location of the entire VM, but don’t want to specify the path twice? Override placement of the VM using Path, but provide only the file name for NewVHDPath:

New-VM -Name 'demovm' -MemoryStartupBytes 2gb -Generation 2 -SwitchName 'vSwitch' -Path 'C:LocalVMs' -NewVHDPath 'demovm.vhdx' -NewVHDSizeBytes 60gb

The above cmdlet creates a “demovm” folder in “C:LocalVMs”. It places the virtual machine’s configuration files in a “Virtual Machines” subfolder and places the new VHDX in a “Virtual Hard Disks” subfolder.

Just as before, you can place the VHDX into an entirely different location just by providing the full path.

Notes on VHDX Creation with New-VM

A few points:

  • You must always supply the VHDX’s complete file name. New-VM will not guess at what you want to call your virtual disk, nor will it auto-append the .vhdx extension.
  • You must always supply a .vhdx extension. New-VM will not create a VHD formatted disk.
  • All the rules about second-hops and delegation apply.
  • Paths operate from the perspective of the Hyper-V host. When running remotely, a path like “C:LocalVMs” means the C: disk on the host, not on your remote system.
  • You cannot specify an existing file. The entire cmdlet will fail if the file already exists (meaning that, if you tell it to create a new disk and it cannot for some reason, then it will not create the VM, either).

As with the wizard, New-VM can create only one VHDX and it will always connect to the primary boot location (IDE controller 0 location 0 for Generation 1, SCSI controller 0 location 0 for Generation 2). You can issue additional PowerShell commands to create and attach more virtual disks. We’ll tackle that after we finish with the New-VM cmdlet.

Create a VM with a VHDX, Single Cmdlet, and Specify Boot Order

We have one more item to control with New-VHD: the boot device. Using the above cmdlets, your newly created VM will try to boot to the network first. If you used one of the variants that create a virtual hard disk, a failed network boot will fall through to the disk.

Let’s create a VM that boots to the virtual CD/DVD drive instead:

New-VM -Name 'demovm' -MemoryStartupBytes 2gb -Generation 2 -SwitchName 'vSwitch' -BootDevice CD

You have multiple options for the BootDevice parameter:

  • CD: attach a virtual drive and set it as the primary boot device
  • Floppy: set the virtual floppy drive as the primary boot device; Generation 1 only
  • IDE: set IDE controller 0, location 0 as the primary boot device; Generation 1 only
  • LegacyNetworkAdapter: attach a legacy network adapter and set it as the primary boot device; Generation 1 only
  • NetworkAdapter: set the network adapter as the primary boot device on a Generation 2 machine, attach a legacy network adapter and set it as the primary boot device on a Generation 1 machine
  • VHD: if you created a VHDX with New-VM, then this will set that disk as the primary boot device. Works for both Generation types

The BootDevice parameter does have come with a quirk: if you create a VHD and set the VM to boot from CD using New-VM, it will fail to create the VM. It tries to attach both the new VHD and the new virtual CD/DVD drive to the same location. The entire process fails. You will need to create the VHD with the VM, then attach a virtual CD/DVD drive and modify the boot order or vice versa.

Make Quick Changes to a New VM with PowerShell

You have your new VM, but you’d like to make some quick, basic changes. Set-VM includes all the common settings as well as a few rare options.

Adjust Processor and Memory Assignments

From New-VM, the virtual machine starts off with one virtual CPU and does not use static memory. My preferred new virtual machine, in two lines:

New-VM -Name 'demovm' -Generation 2 -SwitchName 'vSwitch' -NewVHDPath 'demovm.vhdx' -NewVHDSizeBytes 60gb
Set-VM -VMName 'demovm' -ProcessorCount 2 -MemoryStartupBytes 2gb -DynamicMemory -MemoryMinimumBytes 512mb -MemoryMaximumBytes 4gb

Both New-VM and Set-VM include the MemoryStartupBytes parameter. I used it with Set-VM to make the grouping logical.

Some operating systems do not work with Dynamic Memory, some applications do not work with Dynamic Memory, and some vendors (and even some administrators) just aren’t ready for virtualization. In any of those cases, you can do something like this instead:

New-VM -Name 'vm2003era' -Generation 1 -SwitchName 'vSwitch' -NewVHDPath 'vm2003era.vhdx' -NewVHDSizeBytes 60gb
Set-VM -VMName 'vm2003era' -ProcessorCount 2 -MemoryStartupBytes 4gb -StaticMemory

Technically, you can leave off the StaticMemory parameter in the preceding sequence. New-VM always creates a VM with static memory. Use it when you do not know the state of the VM.

Control Automatic Start and Stop Actions

When a Hyper-V host starts or shuts down, it needs to do something with its VMs. If it belongs to a cluster, it has an easy choice for highly-available VMs: move them. For non-HA VMs, it needs some direction. By default, new VMs will stay off when the host starts and save when the host shuts down. You can override these behaviors:

Set-VM -VMName 'demovm' -AutomaticStartAction Start -AutomaticStartDelay 20 -AutomaticStopAction ShutDown

You can use these parameters with any other parameter on Set-VM, and you do not need to include all three of them. If you use the Nothing setting for AutomaticStartAction or if you do not specify a value for AutomaticStartDelay, then it uses a value of 0. AutomaticStartDelay uses a time value of seconds.

AutomaticStartAction has these options (use [Tab] to cycle through):

  • Nothing: stay off
  • Start: always start with the host, after AutomaticStartDelay seconds
  • StartIfRunning: start the VM with the host after AutomaticStartDelay seconds, but only if it was running when the host shut down

Note: I am aware of what appears to be a bug in 2019 in which the VM might not start automatically.

AutomaticStopAction has these options (use [Tab] to cycle through):

  • Save: place the VM into a saved state
  • ShutDown: via the Hyper-V integration services/components, instruct the guest OS to shut down. If it does not respond or complete within the timeout period, force the VM off.
  • TurnOff: Hyper-V halts the virtual machine immediately (essentially like pulling the power on a physical system)

If you do not know what to do, take the safe route of Save. Hyper-V will wait for saves to complete.

Determine Checkpoint Behavior

By default, Windows 10 will take a checkpoint every time you turn on a virtual machine. That essentially gives you an Oops! button. Windows Server has that option, but leaves it off by default. Both Windows and Windows Server use the so-called “Production” checkpoint and fall back to “Standard” checkpoints. You can override all this behavior.

Applicable parameters:

  • CheckpointType: indicate which type of checkpoints to create. Use [Tab] to cycle through the possible values:
    • Disabled: the VM cannot have checkpoints. Does not impact backup’s use of checkpoints.
    • Production: uses VSS in the guest to signal VSS-aware applications to flush to disk, then takes a checkpoint of only the VM’s configuration and disks. Active threads and memory contents are not protected. If VSS in the guest does not respond, falls back to a “Standard” checkpoint.
    • ProductionOnly: same as Production, but fails the checkpoint operation instead of falling back to “Standard”
    • Standard: checkpoints the entire VM, including active threads and memory. Unlike a Production checkpoint, applications inside a VM have no way to know that a checkpoint operation took place.
  • SnaphotFileLocation: specifies the location for the configuration files of a virtual machine’s future checkpoints. Does not impact existing checkpoints. Does not affect virtual hard disk files (AVHD/X files are always creating alongside the parent).
  • AutomaticCheckpointsEnabled: Controls whether or not Hyper-V makes a checkpoint at each VM start. $true to enable, $false to disable.

Example:

Set-VM -VMName 'demovm' -AutomaticCheckpointsEnabled $false -CheckpointType Standard

Honestly, I dislike the names “Production” and “Standard”. I outright object to the way that Hyper-V Manager and Failover Cluster Manager use the term “application-consistent” to describe them. You can read my article about the two types to help you decide what to do.

Control the Automatic Response to Disconnected Storage

In earlier versions of Hyper-V, losing connection to storage meant disaster for the VMs. Hyper-V would wait out the host’s timeout value (sometimes), then kill the VMs. Now, it can pause the virtual machine’s CPU, memory and I/O, then wait a while for storage to reconnect.

Set-VM -VMName 'demovm' -AutomaticCriticalErrorAction Pause -AutomaticCriticalErrorActionTimeout 120

The value of AutomaticCriticalErrorActionTimeout is expressed in minutes. By default, Hyper-V will wait 30 minutes.

Alternatively, you can set AutomaticCriticalErrorAction to None and Hyper-V will kill the VM immediately, as it did in previous versions.

Attach Human-Readable Notes to a Virtual Machine

You can create notes for a virtual machine right on its properties.

Set-VM -VMName 'demovm' -Notes 'Showing off the notes feature'

Jeff Hicks gave this feature a full treatment and extended it.

Advanced VM Creation with PowerShell

To control all of the features of your new VM, you will need to use additional cmdlets. All of the cmdlets demonstrated in this section will follow a VM created with:

New-VM -Name 'demovm' -Generation 2 -SwitchName 'vSwitch' -NewVHDPath 'demovm.vhdx' -NewVHDSizeBytes 60gb

Starting from that base allows me to get where I want with the least level of typing and effort.

Prepare the VM to Use Discrete Device Assignment

Hyper-V has some advanced capabilities to pass through host hardware using Discrete Device Assignment (DDA). Set-VM has three parameters that impact DDA:

  • LowMemoryMappedIoSpace
  • HighMemoryMappiedIoSpace
  • GuestControlledCacheTypes

These have little purpose outside of DDA. Didier Van Hoye wrote a great article on DDA that includes practical uses for these parameters.

Specify Processor Settings on a New VM

All of the ways to create a VM result in a single vCPU with default settings. You can make some changes in the GUI, but only PowerShell reaches everything. Use the Set-VMProcessor cmdlet.

Changing a VM’s Virtual CPU Count in Hyper-V

I always use at least 2 vCPU because it allows me to leverage SMT and Windows versions past XP/2003 just seem to respond better. I do not use more than two without a demonstrated need or when I have an under-subscribed host. We have an article that dives much deeper into virtual CPUs on Hyper-V.

Give our new VM a second vCPU:

Set-VMProcessor -VMName 'demovm' -Count 2

You cannot change the virtual processor count on a running, saved, or paused VM.

Note: You can also change the vCPU count with Set-VM, shown earlier.

Set Hard Boundaries on a VM’s Access to CPU Resources

To set hard boundaries on the minimum and maximum percentage of host CPU resources the virtual machine can access, use the Reserve and Maximum parameters, respectively. These specify the total percentage of host processor resources to be used which depends on the number of vCPUs assigned. Calculate actual resource reservations/limits like this:

Parameter Value / Number of Host Logical Processors * Number of Assigned Virtual CPUs = Actual Value

So, a VM with 4 vCPUs set with a Reserve value of 25 on a host with 24 logical processors will lock about 4% of the host’s total CPU resources for itself. A VM with 6 vCPUs and a Limit of 75 on a host with 16 logical processors will use no more than about 28% of total processing power. Refer to the previously-linked article for an explanation of these settings.

To set all of these values:

Set-VMProcessor -VMName 'demovm' -Count 2 -Reserve 10 -Maximum 80

You do not need to specify any of these values. New-VM and all the GUI tools create a VM with a value of 1 for Count, a value of 0 for Reserve and a value of 100 for Maximum. If you do not specify one of these parameters for Set-VMProcessor, it leaves the value alone. So, you can set the processor Count in one iteration, then modify the Reserve at a later time without disturbing the Count, and then the Maximum at some other point without impacting either of the previous settings.

You can change these settings on a VM while it is off, on, or paused, but not while saved.

Prioritize a VM’s Access to CPU Resources

Instead of hard limits, you can prioritize a VM’s CPU access with Set-VMProcessor’s RelativeWeight parameter. As indicated, the setting is relative. Every VM has this setting. If every VM has the same priority value, then no one has priority. VM’s begin life with a default processor weight of 100. The host’s scheduler gives preference to VMs with a higher processor weight.

To set the VM’s vCPU count and relative processor weight:

Set-VMProcessor -VMName 'demovm' -Count 2 -RelativeWeight 150

You do not need to specify both values together; I only included the Count to show you how to modify both at once on a new VM. You can also include the Reserve and Maximum settings if you like.

Enable Auto-Throttle on a VM’s CPU Access

Tinkering with limits and reservations and weights can consume a great deal of administrative effort, especially when you only want to ensure that no VM runs off with your CPU and drags the whole thing down for everyone. Your first, best control on that is the number of vCPU assigned to a VM. But, when you start to work with high densities, that approach does not solve much. So, Microsoft designed Host Resource Protection. This feature does not look at raw CPU utilization so much as it monitors certain activities. If it deems them excessive, it enacts throttling. You get this feature with a single switch:

Set-VMProcessor -VMName 'demovm' -Count 2 -EnableHostResourceProtection $true

Microsoft does not fully document what this controls. You will need to test it in your environment to determine its benefits.

You can use the EnableHostResourceProtection parameter by itself or with any of the others.

Set VM Processor Compatibility

Hyper-V uses a CPU model that very nearly exposes the logical processor to VMs as-is. That means that a VM can access all of the advanced instruction sets implemented by a processor. However, Microsoft also designed VMs to move between hosts. Not all CPUs use the same instruction set. So, Microsoft implements a setting that hides all instruction sets except those shared by every supported CPU from a manufacturer. If you plan to Live Migrate a VM between hosts with different CPUs from the same manufacturer, use this cmdlet:

Set-VMProcessor -VMName 'demovm' -Count 2 -CompatibilityForMigrationEnabled $true

Employ a related parameter if you need to run unsupported versions of Windows (like NT 4.0 or 2000):

Set-VMProcessor -VMName 'demovm' -CompatibilityForOlderOperatingSystemsEnabled $true

This one time, I did not override Count. Older operating systems did not have the best support for multi-processing, and a lot of applications from that era perform worse with multiple processors.

You can specify $false to disable these features. You can only change them while the VM is turned off. As with the preceding demonstrations, you can use these parameters in any combination with the others, or by themselves.

Change a VM’s NUMA Processor Settings

I have not written much about NUMA. Even the poorest NUMA configuration would not hurt more than a few Hyper-V administrators. If you don’t know what NUMA is, don’t worry about it. I am writing these instructions for people that know what NUMA is, need it, and just want to know how to use PowerShell to configure it for a Hyper-V VM.

Set-VMProcessor provides two of the three available NUMA settings. We will revisit the other one in the Set-VMMemory section below. Use Set-VMProcessor to specify the maximum number of virtual CPUs per NUMA node or the maximum number of virtual NUMA nodes this VM sees per socket.

Set-VMProcessor -VMName 'demovm' -Count 16 -MaximumCountPerNumaNode 2 -MaximumCountPerNumaSocket 8

As before, you can use any combination of these parameters with each other and the previously-shown parameters. Unlike before, mistakes here can make things worse without making anything better.

Enable Hyper-V Nested Virtualization

Want to run Hyper-V on Hyper-V? No problem (anymore). Run this after you make your new VM:

Set-VMProcessor -VMName 'demovm' -Count 4 -ExposeVirtualizationExtensions $true

Note: Enabling virtualization extensions silently disables Dynamic Memory. Only Startup memory will apply.

I have not tested this setting with other hypervisors. It does pass the enabled virtualization features of your CPU down to the guest, so it might enable others. I also did not test this parameter with any parameter other than Count.

Change Memory Settings on a New VM

New-VM always leaves you with static memory. If you don’t provide a MemoryStartupBytes value, it will use a default of one gigabyte. The GUI wizards can enable Dynamic Memory, but will only set the Startup value. For all other memory settings, you must access the VM’s property sheets or turn to PowerShell. We will make these changes with Set-VMMemory.

Note: You can also change several memory values with Set-VM, shown earlier.

Setting Memory Quantities on a VM

A virtual machine’s memory quantities appear on three parameters:

  • Startup: How much memory the virtual machine will have at boot time. If the VM does not utilize Dynamic Memory, this value persists throughout the VM’s runtime
  • MinimumBytes: The minimum amount of memory that Dynamic Memory can assign to the virtual machine
  • MaximumBytes: The maximum amount of memory that Dynamic Memory can assign to the virtual machine

These values exist on all VMs. Hyper-V only references the latter two if you configure the VM to use Dynamic Memory.

Set-VMMemory -VMName 'demovm' -StartupBytes 2gb

This cmdlet sets the VM to use two gigabytes of memory at the start. It does not impact Dynamic Memory in any way; it leaves all of those settings alone. You can change this value at any time, although some guest operating systems will not reflect the change.

We will include the other two settings in the upcoming Dynamic Memory sections.

Enable Dynamic Memory on a VM

Control whether or not a VM uses Dynamic Memory with the DynamicMemoryEnabled parameter.

Set-VMMemory -VMName 'demovm' -DynamicMemoryEnabled $true

You can disable it with $false. The above usage does not modify any of the memory quantities. A new VM defaults to 512MB minimum and 1TB maximum.

You can only make this change while the VM is off.

You can also control the Buffer percentage that Dynamic Memory uses for this VM. The “buffer” refers to a behind-the-scenes memory reservation for memory expansion. Hyper-V sets aside a percentage of the amount of memory currently assigned to the VM for possible expansion. You control that percentage with this parameter.

Set-VMMemory -VMName 'demovm' -DynamicMemoryEnabled $true -Buffer 10

So, if Hyper-V assigns 2108 megabytes to this VM, it will also have up to 210.8 megabytes of buffered memory. Buffer only sets a maximum; Hyper-V will use less in demanding conditions or if the set size would exceed the maximum assigned value. Hyper-V ignores the Buffer setting when you disable Dynamic Memory on a VM. You can change the buffer size on a running VM.

Dynamic Memory Setting Demonstrations

Let’s combine the above settings into a few demonstrations.

Set-VMMemory -VMName 'demovm' -StartupBytes 2gb -DynamicMemoryEnabled $false
Set-VMMemory -VMName 'demovm' -StartupBytes 2gb -DynamicMemoryEnabled $true -MinimumBytes 512mb -MaximumBytes 4gb
Set-VMMemory -VMName 'demovm' -StartupBytes 2gb -DynamicMemoryEnabled $true -MinimumBytes 512mb -MaximumBytes 24gb -Buffer 5

Control a VM’s Memory Allocation Priority

If VMs have more total assigned memory than the Hyper-V host can accommodate, it will boot them by Priority order (higher first). Also, if Dynamic Memory has to choose between VMs, it will work from Priority.

Set-VMMemory -VMName 'demovm' -StartupBytes 2gb -DynamicMemoryEnabled $true -MinimumBytes 512mb -MaximumBytes 24gb -Buffer 5 -Priority 75

Valid values range from 0 to 100. New VMs default to 50. You can use Priority with any other valid combination of Set-VMMemory. You can change Priority at any time.

Note: The GUI tools call this property Memory weight and show its value as a 9-point slider from Low (0) to High (100).

Change a VM’s NUMA Memory Settings

We covered the processor-related NUMA settings above. Use Set-VMMemory to control the amount of memory in the virtual NUMA nodes presented to this VM:

Set-VMMemory -VMName 'demovm' -MaximumAmountPerNumaNodeBytes 1gb

As with the processor NUMA settings, I only included this to show you how. If you do not understand NUMA and know exactly why you would make this change, do not touch it.

Attach Virtual Disks and CD/DVD Drives to a Virtual Machine

You could use these cmdlets instead of the features of New-VM to attach drives. You can also use them to augment New-VM. Due to some complexities, I prefer the latter.

A Note on Virtual Machine Drive Controllers

On a physical computer, you have to use the physical drive controllers as you find them. If you run out of disk locations, you have to add physical controllers. With Hyper-V, you do not directly manage the controllers. Simply instruct the related cmdlets to attach the drive to a specific controller number and location. As long as the VM does not already have a drive in that location, it will use it.

On a Generation 1 virtual machine, you have two emulated Enhanced Integrated Drive Electronics (EIDE, or just IDE) controllers, numbered 0 and 1. Each has location 0 and location 1 available. That allows a total of four available IDE slots. When you set a Generation 1 VM to boot to IDE or VHD, it will always start with IDE controller 0, position 0. If you set it to boot to CD, it will walk down through 0:0, 0:1, 1:0, and 1:1 to find the first CD drive.

Both Generation types allow up to four synthetic SCSI controllers, numbered 0 through 4. Each controller can have up to 64 locations, numbered 0 through 63.

Unlike a physical system, you will not gain benefits from balancing drives across controllers.

Create a Virtual Hard Disk File to Attach

You can’t attach a disk file that you don’t have. You must know what you will call it, where you want to put it, and how big to make it.

New-VHD -Path '\svstore01vmsVirtual Hard Disksdemodisk.vhdx' -SizeBytes 20gb

By default, New-VHD creates a dynamically-expanding hard disk. For the handful of cases where fixed makes more sense, override with the Fixed parameter:

New-VHD -Path '\svstore01vmsVirtual Hard Disksdemodisk-fixed.vhdx' -SizeBytes 5gb -Fixed

By default a dynamically-expanding VHDX uses a 32 megabyte block size. For some file systems, like ext4, that can cause major expansion percentages over very tiny amounts of utilized space. Override the block size to a value as low as 1 megabyte:

New-VHD -Path '\svstore01vmsVirtual Hard Disksdemodisk.vhdx' -SizeBytes 20gb -BlockSizeBytes 1mb

You can also use LogicalSectorSizeBytes and PhysicalSectorSizeBytes to override defaults. Hyper-V will detect the underlying physical storage characteristics and choose accordingly, so do not override these values unless you intend to migrate the disk to a system with different values:

New-VHD -Path '\svstore01vmsVirtual Hard Disksdemodisk.vhdx' -SizeBytes 20gb -LogicalSectorSizeBytes 4096 -PhysicalSectorSizeBytes 4096

Create a Virtual Hard Disk from a Physical Disk

You can instruct Hyper-V to encapsulate the entirety of a physical disk inside a VHDX. First, use Get-Disk to find the disk number. Then use New-VHD to transfer its contents into a VHD:

New-VHD -SourceDisk 4 -Path '\svstore01vmsVirtual Hard Disksfromdisk4.vhdx'

You can combine this usage with Fixed or BlockSizeBytes (not both). The new VHDX will have a maximum size that matches the source disk.

Create a Child Virtual Hard Disk

In some cases, you might wish to use a differencing disk with a VM, perhaps as its primary disk. This usage allows the VM to operate normally, but prevent it from making changes to the base VHDX file.

New-VHD -Path '\svstore01vmsVirtual Hard Diskschild.vhdx' -ParentPath '\svstore01vmsVirtual Hard Disksbasevhdx.vhdx'

You can also specify the Differencing parameter, if you like.

Note: Any change to the base virtual hard disk invalidates all of its children.

Check a VM for Available Virtual Hard Disk and CD/DVD Locations

You do not need to decide in advance where to connect a disk. However, you sometimes want to have precise control. Before using any of the attach cmdlets, consider verifying that it has not already filled the intended location. Get-VMHardDiskDrive and Get-VMDvdDrive will show current attachments.

Attach a Virtual Hard Disk File to a Virtual Machine

You can add a disk very easily with Add-VMHardDiskDrive:

Add-VMHardDiskDrive -VMName 'demovm' -Path '\svstore01VMsVirtual Hard Diskssecondvhdx.vhdx'

Hyper-V will attach it to the next available location.

You can override to a particular location:

Add-VMHardDiskDrive -VMName 'demovm' -Path '\svstore01VMsVirtual Hard Disksbasevhdx.vhdx' -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 8

Technically, you can skip the ControllerType parameter; Generation 1 assumes IDE and Generation 2 has no other option.

If you want to attach a disk to another SCSI controller, but it does not have another, then add it first:

Add-VMScsiController -VMName 'demovm'
Add-VMHardDiskDrive -VMName 'demovm' -Path '\svstore01VMsVirtual Hard Disksbasevhdx.vhdx' -ControllerNumber 1

Notice that I did not specify a location on Add-VMHardDiskDrive. If you specify a controller but no location, it just uses the next available.

Attach a Virtual DVD Drive to a Virtual Machine

Take special note: this cmdlet applies to a virtual drive, not a virtual disk. Basically, it creates a place to put a CD/DVD image, but does not necessarily involve a disk image. It can do both, as you’ll see.

Add-VMDvdDrive -VMName 'demovm'

Add-VMDvdDrive uses all the same parameters as Add-VMHardDiskDrive above. If you do not specify the Path parameter, then the drive remains empty. If you do specify Path, it mounts the image immediately:

Add-VMDvdDrive -VMName 'demovm' -Path \svstore01isosen_windows_server_2019_x64_dvd_4cb967d8.iso

All the notes from the beginning about permissions and delegation apply here.

If you have a DVD drive already and just want to change its contents, use Set-VMDvdDrive:

Set-VMDvdDrive -VMName 'demovm' -Path \svstore01isossoftware_installer.iso

If you have more than one CD/DVD attached, you can use the ControllerTypeControllerNumber, and ControllerLocation parameters to specify.

If you want to empty the drive:

Set-VMDvdDrive -VMName 'demovm' -Path ''

Remove-VMDvdDrive completely removes the drive from the system.

Work with a New Virtual Machine’s Network Adapters

Every usage of New-VM should result in a virtual machine with at least one virtual network adapter. By default, it will not attach it to a virtual switch. You might need to modify a VLAN. If desired, you can change the name of the adapter. You can also add more adapters, if you want.

Attach the Virtual Adapter to a Virtual Switch

You can connect every adapter on a VM to the same switch:

Connect-VMNetworkAdapter -VMName 'demovm' -SwitchName vSwitch

If you want to specify the adapter, you have to work harder. I wrote up a more thorough guide on networking that includes that, and other advanced topics.

Connect the Virtual Adapter to a VLAN

All of the default vNIC creation processes leave the adapter as untagged. To specify a VLAN, use Set-VMNetworkAdapterVLAN:

Set-VMNetworkAdapterVlan -VMName 'demovm' -Access -VlanId 42

If you need help selecting a vNIC for this operation, use my complete guide for details. It does not have a great deal of information on other ways to use this cmdlet, such as for trunks, so refer to the official documentation.

Rename the Virtual Adapter

You could differentiate adapters for the previous cmdlets by giving adapters unique names. Otherwise, Hyper-V calls them all “Network Adapter”.

Rename-VMNetworkAdapter -VMName 'demovm' -NewName 'Adapter 1'

Like the preceding cmdlets, this usage will rename all vNICs on the VM. Use caution. But, if you do this on a system with only one adapter, then add another, you can filter against adapters not name “Adapter 1”, then later use the VMNetworkAdapterName parameter.

Add Another Virtual Adapter

You can use Add-VMNetworkAdapter to add further adapters to the VM:

Add-VMNetworkAdapter -VMName 'demovm'

Even better, you can name it right away:

Add-VMNetworkAdapter -VMName 'demovm' -Name 'Adapter 2'

Don’t forget to connect your new adapter to a virtual switch (you can still use Connect-VMNetworkAdapter, of course):

Add-VMNetworkAdapter -VMName 'demovm' -Name 'Adapter 2' -SwitchName 'vSwitch'

Add-VMNetworkAdapter has several additional parameters for adapter creation. Set-VMNetworkAdapter has a superset, show I will show them in its context. However, you might find it convenient to use StaticMacAddress when creating the adapter.

Set the MAC Address of a Virtual Adapter

You can set the MAC address to whatever you like:

Set-VMNetworkAdapter -VMName 'demovm' -StaticMacAddress 00155da182d2

If you need to override the MAC for spoofing (as in, for a software load-balancer):

Set-VMNetworkAdapter -VMName 'demovm' -StaticMacAddress 00155da182d2 -MacAddressSpoofing On

Other Virtual Network Adapter Settings

Virtual network adapters have a dizzying array of options. Check the official documentation or Get-Help Set-VMNetworkAdapter to learn about them.

Work with a New Virtual Machine’s Integration Services Settings

None of the VM creation techniques allow you to make changes to the Hyper-V integration services. Few VMs ever need such a change, so including them would amount to a nuisance for most of us. We do sometimes need to change these items, perhaps to disable time synchronization for virtualized domain controllers or to block attempts to signal VSS in Linux guests.

We do not use “Set” cmdlets to control integration services. We have Get-, Enable-, and Disable- for integration services. Every new VM enables all services except “Guest Services”. Ideally, the cmdlets would all have pre-set options for the integration services. Unfortunately, we have to either type them out or pipe them in from Get-VMIntegrationService. You can use it to get a list of the available services. You can then use the selection capabilities of the console to copy and paste the item that you need (draw over the letters to copy, then right-click to paste). You can also use a filter (Where-Object) to pick the one that you need. For now, we will see the simplest choices.

To disable the time synchronization service for a virtual machine:

Disable-VMIntegrationService -VMName demovm -Name 'Time Synchronization'

To enable guest services for a virtual machine:

Enable-VMIntegrationService -VMName demovm -Name 'Guest Service Interface'

Most of the integration service names contain spaces. Don’t forget to use single or double quotes around such a name.

Put it All Together: Use PowerShell to Make the Perfect VM

A very common concern: “How can I remember all of this?” Well, you can’t remember it all. I have used PowerShell to control VMs since the unofficial module for 2008. I don’t remember everything. But, you don’t need to try. In the general sense, you always have Get-Help and Get-Command -Module Hyper-V. But, even better, you probably won’t use the full range of capability. Most of us create VMs with a narrow range of variance. I will give you two general tips for making the VM customization process easier.

Use a Text Tool to Save Creation Components

In introductory, training, and tutorial materials, we often make a strong distinction between interactive PowerShell and scripted PowerShell. If you remember what you want, you can type it right in. If you make enough VMs to justify it, you can have a more thorough script that you guide by parameter. But, you can combine the two for a nice middle ground.

First, pick a tool that you like. Visual Studio Code has a lot of features to support PowerShell. Notepad++ provides a fast and convenient scratch location to copy and paste script pieces.

This tip has one central piece: as you come up with cmdlet configurations that you will, or even might, use again, save them. You don’t have to build everything into a full script. Sometimes, you need a toolbox with a handful of single-purpose snippets. More than once in my career, I’ve come up with a clever solution to solve a problem at hand. Later, I tried to recall it from memory, and couldn’t. Save those little things — you never know when you’ll need them.

Use PowerShell’s Pipeline and Variables with Your Components

In all the cmdlets that I showed you above, I spelled out the virtual machine’s name. You could do a lot of text replacement each time you wanted to use them. But, you have a better way. If you’ve run New-VM lately, you probably noticed that it emitted something to the screen:

customize vms using powershell

Instead of just letting all that go to the screen, you can capture it or pass it along to another cmdlet.

Pipeline Demo

Use the pipe character | to quickly ship output from one cmdlet to another. It works best to make relatively few and simple changes.

New-VM -VMName 'demovm' -Generation 2 -SwitchName 'vSwitch' -NewVHDPath 'demovm-c.vhdx' -NewVHDSizeBytes 60gb | Set-VM -ProcessorCount 2 -DynamicMemory -MemoryStartupBytes 2gb -MemoryMinimumBytes 512mb -MemoryMaximumBytes 4gb -Passthru | Enable-VMIntegrationService -Name 'Guest Service Interface'

The above has three separate commands that all chain from the first. You can copy this into your text manipulation tool and save it. You can then use it as a base pattern. You change the name of the VM and its VHDX in the text tool and then you can create a VM with these settings anytime you like. No need to step through a wizard and then flip a lot of switches after.

Warning: Some people in the PowerShell community develop what I consider an unhealthy obsession with pipelining, or “one liners”. You should know how the pipeline works, especially the movement of objects. But, extensive pipelining becomes impractical quite quickly. Somewhere along the way, it amounts to little more than showing off. Worse, because not every cmdlet outputs the same object, you quickly have to learn a lot of tricks that do nothing except keep the pipeline going. Most egregiously, “one liners” impose severe challenges to legibility and maintainability with no balancing benefit. Use the pipeline to the extent that it makes things easier, but no further.

Variables Demos

You can capture the output of any cmdlet into a variable, then use that output in succeeding lines. It requires more typing than the pipeline, but trades flexibility.

$NewVM = New-VM 'demovm' -Generation 2 -SwitchName 'vSwitch' -NewVHDPath "$VMName-c.vhdx" -NewVHDSizeBytes 60gb
Set-VMProcessor -VM $NewVM -Count 2 -CompatibilityForMigrationEnabled $true
Set-VMMemory -VM $NewVM -StartupBytes 2gb -MinimumBytes 512mb -MaximumBytes 4gb -DynamicMemoryEnabled $true -Buffer 5
Set-VMNetworkAdapterVlan -VM $NewVM -Access -VlanId 20

Each of the cmdlets in the above listing has a PassThru parameter, but, except for New-VM, none emits an object that any of the others can use. This script takes much more typing than the pipeline demo, but it does more and breaks each activity out into a single, easy comprehensible line. As with the pipeline version, you can set up each line to follow the pattern that you use most, then change only the name in the first line to suit each new VM. Notice that it automatically gives the VHDX a name that matches the VM, something that we couldn’t do in the pipeline version.

Combining Pipelines and Variables

You can use variables and pipelines together to maximize their capabilities.

$VMName = 'demovm'
$NewVM = New-VM -VMName $VMName -Generation 2 -SwitchName 'vSwitch' -NewVHDPath "$VMName-c.vhdx" -NewVHDSizeBytes 60gb
$NewVM | Set-VM -ProcessorCount 2 -DynamicMemory -MemoryStartupBytes 2gb -MemoryMinimumBytes 512mb -MemoryMaximumBytes 4gb -Passthru | Enable-VMIntegrationService -Name 'Guest Service Interface'
Set-VMNetworkAdapterVlan -VM $NewVM -Access -VlanId 20

With this one, you can implement your unique pattern but place all the changeable items right in the beginning. This sample only sets the VM’s name. If you want to make other pieces easily changeable, just break them out onto separate lines.

Making Your Own Processes

If you will make a single configuration of VM repeatedly, you should create a saved script or an advanced function in your profile. It should have at least one parameter to specify the individual VM name.

But, even though most people won’t create VMs with a particular wide variance of settings, neither will many people create VMs with an overly tight build. Using a script with lots of parameters presents its own challenges. So, instead of a straight-through script, make a collection of copy/pasteable components.

Use something like the following:

$VMName = 'demovm'

# Gen 1
$NewVM = New-VM -VMName $VMName -SwitchName 'vSwitch' -NewVHDPath 'demovm-c.vhdx' -NewVHDSizeBytes 60gb

# Gen 2
$NewVM = New-VM -VMName $VMName -Generation 2 -SwitchName 'vSwitch' -NewVHDPath 'demovm-c.vhdx' -NewVHDSizeBytes 60gb

# default parts
$NewVM | Set-VM -ProcessorCount 2 -DynamicMemory -MemoryStartupBytes 2gb -MemoryMinimumBytes 512mb -MemoryMaximumBytes 4gb -Passthru | Enable-VMIntegrationService -Name 'Guest Service Interface'
Set-VMNetworkAdapterVlan -VM $NewVM -Access -VlanId 20

# fixed memory
Set-VMMemory -VM $NewVM -DynamicMemoryEnabled $false -StartupBytes 4gb

# vlan
$VLANID = 12
Set-VMNetworkAdapterVlan -VM $NewVM -Access -VlanId $VLANID

# add data disk
$DataDiskSize = 20gb
$DataDisk = New-VHD -Path "\svstore01vmsVirtual Hard Disks$VMName-data.vhdx" -SizeBytes $DataDiskSize
Add-VMHardDiskDrive -VM $NewVM -Path $DataDisk.Path

# setup bootable disk
$BootDiskPath = '\svstore01ISOsWS2016setup.iso'
$BootDiskPath = '\svstore01ISOsWS2019setup.iso'
$BootDiskPath = '\svstore01ISOsUbuntu1904.iso'
$BootDisk = Add-VMDvdDrive -VM $NewVM -Path $BootDiskPath
Set-VMBios -VM $NewVM -StartupOrder CD
Set-VMFirmware -VM $NewVM -FirstBootDevice $BootDrive

Trying to run all of that as-is would cause some problems. Instead, copy out the chunks that you need and paste them as necessary. Add in whatever parts suit your needs.

Be sure to let us know how you super-charged your VM creation routines!

The post How to Customize Hyper-V VMs using PowerShell appeared first on Altaro DOJO | Hyper-V.

]]>
https://www.altaro.com/hyper-v/customize-vm-powershell/feed/ 0
3 Awesome Uses for the Get-VMHostStatus PowerShell Function https://www.altaro.com/hyper-v/get-vmhoststatus/ https://www.altaro.com/hyper-v/get-vmhoststatus/#comments Thu, 02 May 2019 16:34:44 +0000 https://www.altaro.com/hyper-v/?p=17393 PowerShell master Jeff Hicks dishes up 3 ways to use the same custom command that gets status details of your Hyper-V Hosts. Lots of code for you to try!

The post 3 Awesome Uses for the Get-VMHostStatus PowerShell Function appeared first on Altaro DOJO | Hyper-V.

]]>

In a recent article, I showed you how I built a PowerShell function to display information about the status of a Hyper-V host. The function alone I hope is useful. But what good would it be if that is all it does?  Actually, let me rephrase that – what else can I do with that function? The whole point of PowerShell is that it provides a large set of building blocks that you can assemble to achieve the desired result.

A simple PowerShell pipelined expression

It can be as simple as the illustrated command above. For more complex tasks and especially those that you want to repeat, you will take the same commands you would run at a prompt and put them into a PowerShell script. That’s what I want to show you today. Using the Get-VMHostStatus function, here are 3 different ways you might leverage it. I won’t go into great detail on the code as I’ve added internal comments. And as with all my articles, pay as much attention to the techniques and concepts as much as the end result. You may not have a need for any of these scripts as they exist, but you might pick up an idea that you can use in building your own Hyper-V PowerShell tools.

Creating an HTML Report

My first use is to take the output from Get-VMHostStatus and create an HTML report. Even though the function is creating an object with a lot of information, I can pick and choose what I want. I can even gather additional information. The script is essentially a wrapper for Get-VMHostStatus. It runs the command and creates a series of HTML fragments which is then assembled into a final file. The script even embeds a CSS style sheet.

#requires -version 5.1

[cmdletbinding()]
Param(
    [Parameter(Position = 0 , Mandatory, HelpMessage = "Enter the name of a Hyper-V Host")]
    [string]$ComputerName,
    [Paramater(ParameterSetName = "Computername")]
    [PSCredential]$Credential,
   
    [ValidatePattern("w+.(html|htm)$")]
    [string]$Path = ".HyperVHostStatus.htm"
)

#region get VM Host Status

#dot source the required script with the Get-VMHostStatus function
. C:scriptsGet-VMHostStatus.ps1

#remove the Path from boundparameters since it isn't part of the parameters
#for Get-VMHostStatus and I want to eventually splat $PSBoundParameters
if ($PSBoundParameters.ContainsKey("Path")) {
    $PSBoundParameters.Remove("Path") | Out-Null
}

#get the VMHost data passing bound parameters
Try {
    $data = Get-VMHostStatus @PSBoundParameters -ErrorAction Stop
}
Catch {
    Throw $_
}

#endregion

#region define the CSS style in the head section

#get sample CSS from https://github.com/jdhitsolutions/SampleCSS
$head = @"
<Title>Hyper-V Host Status</Title>
<style>
@charset "UTF-8";

body {
    background-color: rgb(233, 223, 223);
    font-family: Monospace;
    font-size: 12pt;
}

td,th {
    border: 0px solid black;
    border-collapse: collapse;
    white-space: pre;
}

th {
    color: white;
    background-color: black;
}

table,tr,td,th {
    padding: 3px;
    margin: 0px;
    white-space: pre;
}

tr:nth-child(odd) {
    background-color: lightgray
}

table {
    margin-left: 25px;
    width: 50%;
}

h2 {
    font-family: Tahoma;
}

.footer {
    color: green;
    margin-left: 25px;
    font-family: Tahoma
    font-size: 7pt;
    font-style: italic;
}
</style>
"@

#endregion

#region get some additional data

$s = New-PSSession @PSBoundParameters

$procdata = Invoke-command -ScriptBlock {Get-Ciminstance Win32_Computersystem -Property 'NumberOfLogicalProcessors', 'NumberOfProcessors'} -session $s
$hostdetail = Invoke-Command -ScriptBlock {Get-Ciminstance Win32_OperatingSystem -property "Caption", "Version"} -session $s
$detail = "{0} version {1}" -f $hostdetail.caption, $hostdetail.version
Remove-PSSession $s

#endregion

#region define the pieces of the HTML report as fragments

$fragments = @()
$fragments += "<H1>Hyper-V Host Status</H1>"
$fragments += "<H2 title = '$detail'>$($data.computername)<H2>"

$fragments += "<H3>Memory</H3>"
$fragments += $data | Select-object -Property *memory*, TotalPctDemand | ConvertTo-Html -Fragment -as List
$fragments += "<H3>Processor</H3>"

$fragments += $data | Select-Object -Property @{Name = "ProcessorCount"; Expression = {$procdata.NumberOfProcessors}},
@{Name = "LogicalProcessorCount"; Expression = {$procdata.NumberOfLogicalProcessors}},
PctProcessorTime, Logical* | ConvertTo-Html -Fragment -as list

$fragments += "<H3>Virtual Machines</H3>"
$fragments += $data | Select-Object -Property *VMs | ConvertTo-Html -Fragment -as List

$fragments += "<H3>Virtual Machine Health</H3>"
$fragments += $data | Select-Object -Property Critical, Healthy | ConvertTo-Html -Fragment -as table

#a future version might include additional network-related values
$fragments += "<H3>Networking</H3>"
$fragments += $data | Select-Object VMSwitchBytesSec, VMSwitchPacketsSec | ConvertTo-Html -Fragment

$fragments += "<H3>Other</H3>"
$fragments += $data | Select-Object Uptime, TotalProcesses, PctFreeDisk| ConvertTo-Html -Fragment -as table

#endregion

#region create an object with footer information so it can be displayed neatly

[xml]$meta = [pscustomobject]@{
    "Report run"  = (Get-Date)
    Author        = "$env:USERDOMAIN$env:USERNAME"
    Script        = (Convert-Path $MyInvocation.InvocationName).Replace("", "/")
    ScriptVersion = '0.9.3'
    Source        = $env:COMPUTERNAME
} | ConvertTo-Html -Fragment -As List

$meta.CreateAttribute("Class") | Out-Null
$meta.table.SetAttribute("class", "footer")

#endregion

#region assemble the final HTML report

ConvertTo-Html -Head $head -body $fragments -postcontent "<br><br>$($meta.innerxml)"  |
    Out-File -FilePath $Path -Encoding utf8

Write-Host "See $(Convert-Path $path) for the finished report file." -ForegroundColor green

#endregion

The script will create a single HTML report per Hyper-V Host.

C:scriptsGet-VMHostStatusReport.ps1 -computername chi-p50 -path $env:tempchi-p50-status.html

The end result looks like this:

A Hyper-V Host Status HTML Report

One thing I want to point out is that I’ve added some metadata at the bottom of the report to indicate how and where the file originated from. This is the type of script you could set up as a PowerShell Scheduled Job. Maybe even emailing the results. Over time and as staff turns over, people may forget where the report is coming from. Adding the metadata helps you keep tabs on where the script is running.

Extending the PowerShell Prompt

Let’s say managing Hyper-V is a primary job function. If you are like me you may have a PowerShell window open constantly. I’ve been writing on my blog a number of articles that demonstrate how to turn your PowerShell prompt into a monitoring tool. I realized I could do something similar with a Hyper-V emphasis.

Function prompt {

    #define a hashtable of symbols to use
    $charHash = @{
        Up          = [char]0x25b2
        Down        = [char]0x25bc
        Delta       = [char]0x2206
        Pointer     = [char]0x25BA
        Pointerleft = [char]0x25C4
        TopLeft     = [char]0x250c
        TopRight    = [char]0x2510
        Border      = [char]0x2500
        BottomLeft  = [char]0x2514
        BottomRight = [char]0x2518
        Ohm         = [char]0x2126
        Mu          = [char]0x3bc
        disk        = [char]0x058d
        bps         = [char]0x20bf
    }

    Try {
        #verify there is a global hashtable variable
        Get-Variable -Name rsHash -Scope global -ErrorAction Stop | Out-Null
    }
    Catch {
        #create the runspace and synchronized hashtable
        $global:rsHash = [hashtable]::Synchronized(@{Computername = $env:computername; results = ""; date = (Get-Date); computers = @()})
        $newRunspace = [runspacefactory]::CreateRunspace()
        #set apartment state if available
        if ($newRunspace.ApartmentState) {
            $newRunspace.ApartmentState = "STA"
        }
        $newRunspace.ThreadOptions = "ReuseThread"
        $newRunspace.Open()
        $newRunspace.SessionStateProxy.SetVariable("rsHash", $rsHash)

        $pscmd = [PowerShell]::Create().AddScript( {

                #define scriptblock to run in the background
                $sb = {
                    Param($sessions)
                    #turn off Write-Progress to speed things up a bit
                    $ProgressPreference = "silentlycontinue"
                    #dot source the script file
                    . C:scriptsGet-VMHostStatus.ps1
                    #run the function using the PSSessions
                    Get-VMHostStatus $sessions
                }

                #define the Hyper-V Hosts to query as a comma separated list
                $Computers= $env:computername
                #or pull names in from a text file
                # $computers = Get-Content c:scriptshvhosts.txt

                do {
                    #reset results
                    #make sure there are sessions for all computers in the list
                    $computers | Where-Object {(get-pssession).where( {$_.state -eq 'opened'}).computername -notcontains $_} |
                        ForEach-Object {
                        New-PSSession -ComputerName $_
                    }
                    Get-PSSession | Where-Object {$_.state -eq 'broken'} | foreach-object {
                        Remove-PSSession $_
                        #attempt to recreate it
                        New-PSsession -ComputerName $_.computername
                    }
                    $results = Invoke-Command -ScriptBlock $sb -ArgumentList @(, $(Get-PSSession))

                    $global:rsHash.results = $results
                    $global:rsHash.date = Get-Date
                    $global:rshash.computers = $computers
                    #set a sleep interval between tests
                    Start-Sleep -Seconds 10
                } While ($True)
            }) # script
        $pscmd.runspace = $newrunspace
        [void]$psCmd.BeginInvoke()
    } #catch

    if (-Not $global:rsHash.results) {
        #define a working message that has the string: Working
        $workingmsg = " Working....please wait"
        $working = $True
    }
    else {
        $data = $global:rsHash.results
        $working = $False
        #get the length of the longest host name for padding purposes
        $pad = ($data.computername | Sort-object {$_.length} -Descending | Select-Object -first 1).length
    }

    #Take a guess at how wide to make the border
    $wide = 68

    #display the results in the console
    Write-Host "`n " -NoNewline
    Write-Host "Hyper-V Host Status $(Get-Date)" -BackgroundColor Gray -ForegroundColor Black
    Write-Host $charHash.TopLeft -NoNewline
    Write-Host $($charHash.Border.tostring() * $wide) -NoNewline
    Write-Host $charHash.TopRight

    if ($working) {
        Write-Host $workingmsg -ForegroundColor Yellow
    }
    else {

        foreach ($name in $($global:rsHash.computers)) {
            $hvhost = $data | where {$_.computername -eq $name}
            if (-not $Hvhost) {
                #create an empty placeholder
                $hvhost = [psobject]@{
                    Uptime = New-timespan
                    RunningVMs = 0
                    OffVMs = 0
                    PausedVMs = 0
                    SavedVMs = 0
                    PctMemoryFree = 0
                    TotalPctDemand = 0
                    PctFreeDisk = 0
                }
            }
            $hostinfo =" $($charHash.Pointer) {0}" -f $name.Padright($pad," ")
            Write-Host $hostInfo -NoNewline

            if ($hvhost.Uptime.totalseconds -gt 0) {
                write-Host " $($charHash.up)" -ForegroundColor Green -NoNewline
            }
            else {
                Write-Host " $($charHash.Down)" -ForegroundColor Red -NoNewline
            }

            write-Host (" {0:ddd.hh:mm:ss}" -f $hvhost.Uptime) -NoNewline

            Write-Host " $($charhash.up) $($hvhost.runningVMs)" -ForegroundColor Green -NoNewline
            Write-Host " $($charhash.down) $($hvhost.offVMs)" -ForegroundColor red -NoNewline
            Write-Host " $($charhash.pointer)$($charHash.pointerleft) $($hvhost.pausedVMs)" -ForegroundColor magenta -NoNewline
            Write-Host " $($charHash.pointerleft)$($charhash.pointer) $($hvhost.savedVMs)" -ForegroundColor yellow -NoNewline
            #define some threshholds which will be reflected in color
            if ($hvhost.PctMemoryFree -le 20) {
                $fg = "red"
            }
            elseif ($hvhost.PctMemoryFree -le 60) {
                $fg = "yellow"
            }
            else {
                $fg = "green"
            }

            Write-Host " $($charHash.mu)$(($hvhost.pctMemoryFree.tostring()).Padleft(5,' '))%" -NoNewline -ForegroundColor $fg
            Write-Host " $($charhash.delta)$(($hvhost.TotalPctDemand.tostring()).Padleft(5,' '))" -NoNewline
            if ($hvhost.PctFreeDisk -le 20) {
                $fg = "red"
            }
            elseif ($hvhost.PctFreeDisk -le 40) {
                $fg = "yellow"
            }
            else {
                $fg = "green"
            }
            #format pctFreeDisk
            [string]$p =[math]::round($hvhost.pctFreeDisk,2)
            Write-Host (" $($charHash.disk){0}" -f $p.padleft(5,' ')) -ForegroundColor $fg
        }
    }

    # Add the bottom of the border
    Write-Host $charHash.BottomLeft -NoNewline
    Write-Host $($charHash.Border.tostring() * $wide) -NoNewline
    Write-Host $charHash.BottomRight

    "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "

} #close function

You will need to specify the location and filename of the script file with the Get-HVHostStatus prompt and you might need to define $computers with a comma-separated list of server names. The function defaults to the localhost. I would keep this list to a handful of server names.

To use this, save the script to a file and dot source it in your PowerShell session.

. C:scriptshvprompt.ps1

Or dot source it in your PowerShell profile script. When the prompt initially loads you’ll see a “Working” message. In the background the prompt is spinning up a separate runspace, running Get-VMHostStatus and passing the results via a synchronized hash table. Yeah, this is a bit advanced so if you feel lost that is to be expected. But once you make the necessary changes for your environment you should see something like this:

A PowerShell Prompt with Hyper-V Host Status Details

If the host is offline, you’ll see a red downward pointing indicator and 0 in all the values. Reading from left to right this is what the prompt is showing:

  • Computername
  • Up/Down indicator
  • Uptime
  • Number of running virtual machines
  • Number of stopped virtual machines
  • Number of paused virtual machines
  • Number of saved virtual machines
  • The percent free memory (color coded)
  • The total percent memory demand
  • The percent free disk space for the default storage drive  (color coded)

This isn’t necessarily real-time data. In the background, the information is getting updated every 10 seconds. You may want to increase that value. Every time you press Enter you are getting the last obtained values.

Creating a Graphical Monitor

The last solution is a bit more complicated. I wanted to take advantage of my Convertto-WPFGrid function which I’ve written about before. I wanted to use it as a standalone graphical reporting tool that you could kick off from PowerShell yet wouldn’t block your PowerShell prompt. This was more complicated than I expected and ended up building a set of functions that start the WPF (Windows Presentation Foundation) code in a background runspace. The WPF display can be managed through the synchronized hashtable. But I didn’t want to force you to know how to modify the hashtable so I wrote a few helper functions. Here’s the complete script file.

# requires -version 5.1

<#

MonitorHVHost.ps1

This is NOT a module so you will need to dot source the script file into
your PowerShell session. You can then run Start-HVHostMonitor to kick off
a WPF GUI. Use Set-HVHostMonitor to adjust it and Stop-HVHostMonitor to
clean up.

Allow a little time for changes to be updated.

The properties are from Get-VMHostStatus

Computername                    : CHI-P50
Uptime                          : 00:46:26.8179350
PctProcessorTime                : 3.84799996455453
TotalMemoryGB                   : 64
PctMemoryFree                   : 77.16
TotalVMs                        : 13
RunningVMs                      : 4
OffVMs                          : 9
SavedVMs                        : 0
PausedVMs                       : 0
OtherVMs                        : 0
Critical                        : 0
Healthy                         : 13
TotalAssignedMemoryGB           : 10.939453125
TotalDemandMemoryGB             : 5.6279296875
TotalPctDemand                  : 8.81
PctFreeDisk                     : 27.3125310680432
VMSwitchBytesSec                : 170332.7095065
VMSwitchPacketsSec              : 263.120591389896
LogicalProcPctGuestRuntime      : 4.11198442672914
LogicalProcPctHypervisorRuntime : 0.28358272876852
TotalProcesses                  : 88

#>

Function Start-HVHostMonitor {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, HelpMessage = "The names of the Hyper-V Hosts to monitor")]
        [ValidateNotNullorEmpty()]
        [alias("cn", "host")]
        #Hyper-V hosts to monitor
        [string[]]$Computername = $env:COMPUTERNAME,
        #properties to display from Get-VMHostStatus
        [Parameter(HelpMessage = "The properties from Get-VMHostStatus")]
        [ValidateNotNullorEmpty()]
        [string[]]$Properties = @("Computername", "Uptime", "*VMs", "Pct*"),
        [Parameter(HelpMessage = "The form height")]
        [int]$Height = 160,
        [Parameter(HelpMessage = "The form width")]
        [int]$Width = 970,
        #the windows title
        [Parameter(HelpMessage = "The form title")]
        [string]$Title = "Hyper-V Host Status",
        [Parameter(HelpMessage = "The refresh interval in seconds")]
        [int]$Timeout = 30
    )

    #define a synchronized hashtable that can be used to "communicate" with the background runspace
    $global:rsHash = [hashtable]::Synchronized(@{

            #Hyper-V hosts to monitor
            computername = @($Computername)

            #properties to display
            properties   = @($Properties)

            height       = $height
            width        = $width
            title        = $Title
            timeout      = $Timeout
            CenterScreen = $False

            #set to false to stop the display
            Run          = $True
            #these properties are for troubleshooting and development
            Data         = @()
            Updated      = (Get-Date)
            Timer        = ""
            JobID        = 0
            Job          = ""
        })

    $newRunspace = [RunspaceFactory]::CreateRunspace()
    $newRunspace.ApartmentState = "STA"
    $newRunspace.ThreadOptions = "ReuseThread"
    $newRunspace.Open()
    $newRunspace.SessionStateProxy.SetVariable("rsHash", $global:rsHash)

    $psCmd = [PowerShell]::Create().AddScript( {

            # It may not be necessary to add these types but it doesn't hurt to include them
            Add-Type -AssemblyName PresentationFramework
            Add-Type -assemblyName PresentationCore
            Add-Type -AssemblyName WindowsBase

            # !!! You will need to update the path to your copy of Get-VMHostStatus.ps1 !!!#
            # !!! The scriptblock should have a period then a space then the path to the ps1 file !!!#
            $script:Init = { . "C:scriptsGet-VMHostStatus.ps1"}
            Function _startjob {
                #this is a private internal function used to start a data gathering job in the background
                Start-Job -ScriptBlock {
                    Param($hosts)
                    #ignore errors for offline or bad hosts
                    Get-VMHostStatus -Computername $hosts -ErrorAction SilentlyContinue
                } -InitializationScript $script:Init -ArgumentList @(, @($global:rsHash.computername))
                $global:rsHash.jobid = $script:j.id
            }

            #get initial data
            $script:j = _startjob
            Do {

                #region WPF code
                # define a timer to automatically dismiss the form. The timer uses a 5 second interval tick
                if ($global:rsHash.Timeout -gt 0) {
                    Write-Verbose "Creating a timer"
                    $timer = new-object System.Windows.Threading.DispatcherTimer
                    $terminate = (Get-Date).AddSeconds($global:rsHash.timeout)
                    Write-verbose "Form will close at $terminate"
                    $timer.Interval = [TimeSpan]"0:0:5.00"

                    $timer.add_tick( {
                            if ((Get-Date) -ge $terminate) {

                                if ($global:rsHash.run) {
                                    $timer.stop()

                                    $data = $script:j | Wait-Job -ov w | Receive-Job | Select-Object $global:rsHash.Properties -OutVariable hash
                                    $global:rshash.job = $w
                                    $global:rsHash.data = $hash
                                    $global:rsHash.updated = (Get-Date)
                                    $datagrid.Clear()
                                    $DataGrid.ItemsSource = $hash
                                    $form.title = "$($global:rsHash.title) [Last Updated $(Get-Date)]"
                                    $form.Height = $global:rsHash.Height
                                    $form.Width = $global:rsHash.Width


                                    $terminate = (Get-Date).AddSeconds($global:rsHash.timeout)
                                    #kick off the next job
                                    $script:j = _startjob
                                    $global:rsHash.timer = (Get-Date)
                                    $timer.Start()
                                    $form.UpdateLayout()
                                }
                                else {
                                    $form.close()
                                }
                            }
                        })
                }

                $form = New-Object System.Windows.Window
                #define what it looks like
                $form.Title = "$($global:rsHash.title) [Last Updated $(Get-Date)]"
                $form.Height = $global:rsHash.Height
                $form.Width = $global:rsHash.Width

                if ($global:rsHash.CenterScreen) {
                    Write-Verbose "Form will be center screen"
                    $form.WindowStartupLocation = [System.Windows.WindowStartupLocation]::CenterScreen
                }
                #define a handler when the form is loaded. The scriptblock uses variables defined later
                #in the script
                $form.add_Loaded( {
                        foreach ($col in $datagrid.Columns) {
                            #because of the way I am loading data into the grid
                            #it appears I need to set the sorting on each column
                            $col.CanUserSort = $True
                            $col.SortMemberPath = $col.Header
                        }
                        $datagrid.Items.Refresh()
                        $form.focus
                    })
                #Create a stack panel to hold the datagrid
                $stack = New-object System.Windows.Controls.StackPanel

                #create a datagrid
                $datagrid = New-Object System.Windows.Controls.DataGrid

                $datagrid.VerticalAlignment = "Bottom"
                #adjust the size of the grid based on the form dimensions
                $datagrid.Height = $form.Height - 50
                $datagrid.Width = $form.Width - 50
                $datagrid.CanUserSortColumns = $True
                $datagrid.CanUserResizeColumns = $True
                $datagrid.CanUserReorderColumns = $True
                $datagrid.AutoGenerateColumns = $True
                #enable alternating color rows
                $datagrid.AlternatingRowBackground = "gainsboro"

                $stack.AddChild($datagrid)
                $form.AddChild($stack)

                #endregion
                #show the form

                $data = $script:j | Wait-Job -ov w | Receive-Job | Select-Object $global:rsHash.Properties -OutVariable hash
                $global:rshash.job = $w
                $global:rsHash.data = $hash
                $global:rsHash.updated = (Get-Date)
                $DataGrid.ItemsSource = $hash
                #kick off the next job
                $script:j = _startjob

                If ($global:rsHash.Timeout -gt 0) {
                    Write-Verbose "Starting timer"
                    $timer.IsEnabled = $True
                    $Timer.Start()
                }

                Write-Verbose "Displaying form"
                $form.Title = "$($global:rsHash.title) [Last Updated $(Get-Date)]"
                $form.ShowDialog() | Out-Null

            } while ($global:rsHash.Run)

        })

    $psCmd.Runspace = $newRunspace
    $psCmd.BeginInvoke() | Out-Null

    $msg = @"
Please wait a moment for the results to be displayed.
To terminate run this command at your prompt"

  `$rsHash.run = `$False

or run:

  Stop-HVHostMonitor

Using runspace id $($newRunspace.Id)
"@

    Write-host $msg -ForegroundColor cyan
    #set a global variable for the runspace so it can later be cleaned up
    $global:hvmonrun = $newrunspace.id
} #close Start function

Function Stop-HVHostMonitor {
    [cmdletbinding(SupportsShouldProcess)]
    Param()

    if ($pscmdlet.ShouldProcess("Hyper-VHost Monitor")) {
        $global:rsHash.run = $False
        Remove-Variable -Name rshash -Scope global
    }

    $run = Get-Runspace -id $global:hvmonrun
    if ($pscmdlet.ShouldProcess("Runspace id $($run.id)", "Clean up")) {
        $run.Close()
        $run.Dispose()
        Remove-Variable -Name hvmonrun -Scope global
    }
} #close Stop function

Function Set-HVHostMonitor {
    [cmdletbinding(SupportsShouldProcess)]
    Param(
        [Parameter(Position = 0, HelpMessage = "The names of the Hyper-V Hosts to monitor")]
        #Hyper-V hosts to monitor
        [ValidateNotNullOrEmpty()]
        [alias("cn", "host")]
        [string[]]$Computername,
        #properties to display
        [Parameter(HelpMessage = "Update the properties from Get-VMHostStatus")]
        [ValidateNotNullorEmpty()]
        [string[]]$Properties,
        [Parameter(HelpMessage = "Update the form height")]
        [int]$Height,
        [Parameter(HelpMessage = "Update the form width")]
        [int]$Width,
        #the windows title
        [Parameter(HelpMessage = "Update yhe form title")]
        [string]$Title,
        [Parameter(HelpMessage = "Update the refresh interval in seconds")]
        [int]$Timeout
    )

    #remove common parameters from bound parameters
    "Whatif", "Verbose", "ErrorAction" | foreach-object {
        if ($PSBoundParameters.ContainsKey($_)) {
            [void]$PSBoundParameters.Remove($_)
        }
    }

    $PSBoundParameters.GetEnumerator() | Foreach-Object {
        if ($pscmdlet.ShouldProcess($_.key, "Update monitor value")) {
            $global:rsHash[$_.key] = $_.value
        }
    }
} #close Set function

This is currently written as a .PS1 file. It would not take much to turn this into a module. If you have the experience, you might want to take that extra step. Once you dot source the script (or import it as a module) you will have these commands:

  • Start-HVHostMonitor
  • Set-HVHostMonitor
  • Stop-HVHostMonitor

You should be able to get help on any of them to see the syntax. To begin monitoring you can use the default parameter values. Or run it like this.

Start-HVHostMonitor -Computername CHI-P50,Bovine320,Think51 -Title "VM Hosts" -Timeout 60

This will start the process and after a brief moment, display a graphical table with selected properties from Get-VMHostStatus. In this example, the display will be updated every minute.

Continuous Display of Hyper-V Host Status in a WPF Grid

You can use Set-HVHostMonitor to adjust display properties on the fly. Although you might not see the results until the next refresh.

As long as the PowerShell session where you started it from is running, the form will be displayed. To terminate it, use the Stop-HVHostMonitor.

Your Turn

And there you have it: 3 different ways to use a single PowerShell command. Behind the scenes I’m executing my Get-VMHostStatus function against one or more Hyper-V hosts. From there I can use the data in whatever way meets my business needs. If you are still getting started with PowerShell, don’t try to create projects as complicated as these. Start simple. With time and experience you’ll soon be creating PowerShell Hyper-V management tools that you’ll wonder how you ever managed without. When you do, I hope you’ll share. I definitely would like to know what you are up to!

Need Help

I’ve mentioned this before, but if you need help with PowerShell or writing a PowerShell script, I encourage you to use the free forums at PowerShell.org.

The post 3 Awesome Uses for the Get-VMHostStatus PowerShell Function appeared first on Altaro DOJO | Hyper-V.

]]>
https://www.altaro.com/hyper-v/get-vmhoststatus/feed/ 2
Free PowerShell Script for Hyper-V: Integration Services on Older Systems https://www.altaro.com/hyper-v/powershell-integration-services/ https://www.altaro.com/hyper-v/powershell-integration-services/#respond Fri, 21 Dec 2018 11:12:48 +0000 https://www.altaro.com/hyper-v/?p=17251 This PowerShell script updates integration services on older systems e.g. Windows 7 guests of a 2012 R2 host filling in the gaps that Windows Update misses

The post Free PowerShell Script for Hyper-V: Integration Services on Older Systems appeared first on Altaro DOJO | Hyper-V.

]]>

Windows Update should cover Integration Services updates for most of us, however, I still have an active need to update Windows 7 guests of a 2012 R2 host, and these machines frequently get re-imaged from a source that I do not control. I looked for a pre-built script, but the ones that I found ranged from non-functional to not adequate. So, I rolled one up and now it’s also yours to enjoy too!

Try Windows Updates for Hyper-V Integration Services Updates

Most people should not need this script anymore. Unlike their predecessors, Windows Server 2016 and later do not include the necessary files. Guests running recent Windows Server operating systems should receive updates in normal Microsoft Update cycles. For all other Windows systems, you’ll need to locate a copy of the necessary Integration Services CAB file(s). You can use them with this script to update a VM offline or copy their installer into the guest operating system’s file system and run them there.

Microsoft Senior PFE Chuck Timon has written an article that discusses which operating systems will receive updates as guests.

Integration Services Updates Work Only for Windows

Linux and BSD guests use “Integration Components”, not Integration Services. I do not know of any external scripts to update them in offline guests. The Linux and BSD kernels have included the base components natively for several versions, so most current distributions will automatically update them along with kernel updates. You can find information on enabling any additional components on the related Microsoft docs pages. For other operating systems, or if you prefer to install the updates out-of-band from kernel upgrades, you can download and install the Linux Integration Services separately.

Script Notes

I included the initial file listing here for convenience. I will publish any future updates exclusively on my GitHub page.

Notes:

  • The script only works on local virtual machines
  • The local machine must have the Hyper-V PowerShell cmdlets installed
  • You must run the script as an administrator
  • The script only requires that you supply one or more VMs (output from Get-VM), or their names
  • By default, the script uses the files from the host. These might be out of date or not even present. Use -Path to designate your preferred files.
  • By default, it will only attempt to install the 64-bit components. Use -x86 for 32-bit components, or -Try32And64 to try both.
  • By default, it looks in the local Windows folder for the necessary CABs. If you’re using on something newer than 2012 R2, you’ll need to provide them yourself. Read the help (in Get-Help or online) for details.
  • The script was built for 2012 R2, so those are the file versions that it expects. If you intend to use different file names, then you’ll need to edit the script file. I left the necessary lines near the top so they’ll be easy to find.
  • The script outputs nothing when it works. If you need verification, use -Verbose. You can safely re-apply the same CAB.

Script File Listing

The text of the script’s initial release appears below:

<#
.SYNOPSIS
Installs the Hyper-V integration services into offline local virtual machines.
.DESCRIPTION
Installs the Hyper-V integration services into offline local virtual machines.
Built specifically to work on Windows Server 2012 R2 guests. Modify the default filenames and override $Path to use a different set of Integration Services.
Use the -Verbose switch for verification of successful installations.
.PARAMETER VM
The name or virtual machine object(s) (from Get-VM, etc.) to update.
.PARAMETER Path
A valid path to the update CABs.
MUST have sub-folders names amd64 and x86 with the necessary Windows6.x-HyperVIntegrationServices-PLATFORM.cab.
You can override the file names by editing the script.
.PARAMETER x86
Use the x86 update instead of x64.
.PARAMETER Try32and64
Attempt to install both the 32-bit and 64-bit updates. Use if you're not sure of the bitness of the contained guest.
.EXAMPLE
C:PS> Update-VMIntegrationServices -VMName vm04
Installs the x64 updates on the VM named vm04.
.EXAMPLE
C:PS> Get-VM | Update-VMIntegrationServices -Try32and64
Attempts to update all VMs. Will try to apply both 32 and 64 bit to see if either is applicable.
.NOTES
Author: Eric Siron
Version 1.0, December 11, 2018
Released under MIT license
.LINK
https://github.com/ejsiron/Posher-V/blob/master/Docs/Update-VMIntegrationServices.md
#>
[CmdletBinding(DefaultParameterSetName='Default')]
param(
    [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true, ParameterSetName='Default')]
    [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true, ParameterSetName='x86Only')]
    [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true, ParameterSetName='TryBoth')]
    [psobject[]]$VM,
    [Parameter(ParameterSetName='Default')]
    [Parameter(ParameterSetName = 'x86Only')]
    [Parameter(ParameterSetName = 'TryBoth')]
    [String]$Path = [String]::Empty,
	[Parameter(ParameterSetName = 'x86Only')][Switch]$x86,
	[Parameter(ParameterSetName = 'TryBoth')][Switch]$Try32and64
)

#requires -Version 4
#requires -RunAsAdministrator
#requires -Module Hyper-V

begin
{
	Set-StrictMode -Version Latest
	$DefaultPath = Join-Path -Path $env:SystemRoot -ChildPath 'vmguestsupport'
	$x64UpdateFile = 'amd64Windows6.x-HyperVIntegrationServices-x64.cab'
	$x86UpdateFile = 'x86Windows6.x-HyperVIntegrationServices-x86.cab'

	if ([String]::IsNullOrEmpty($Path)) { $Path = $DefaultPath }
	$Path = (Resolve-Path -Path $Path -ErrorAction Stop).Path
	$UpdateFiles = New-Object -TypeName System.Collections.ArrayList
	if ($x86 -or $Try32and64)
	{
		$OutNull = $UpdateFiles.Add((Resolve-Path -Path (Join-Path -Path $Path -ChildPath $x86UpdateFile) -ErrorAction Stop).Path)
	}
	if (-not $Try32and64)
	{
		$OutNull = $UpdateFiles.Add((Resolve-Path -Path (Join-Path -Path $Path -ChildPath $x64UpdateFile) -ErrorAction Stop).Path)
	}
}

process
{
    if($VM.Count -eq 0) { exit 0 }
    $VMParamType = $VM[0].GetType().FullName
    switch($VMParamType)
    {
        'Microsoft.HyperV.PowerShell.VirtualMachine' {
            # preferred condition so do nothing; just capture the condition
        }
        'System.String' {
            $VM = Get-VM -Name $VM
        }
        default {
            Write-Error -Message ('Cannot work with objects of type {0}' -f $VMParamType) -ErrorAction Stop
        }
    }

	foreach ($Machine in $VM)
	{
		Write-Progress -Activity 'Adding current integration components to VMs' -Status $Machine.Name -Id 7 # ID just so it doesn't collide with Add-WindowsPackage or *-DiskImage
		if ($Machine.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off)
		{
			$VMHDParams = @{
				VM                 = $Machine;
				ControllerType     = [Microsoft.HyperV.PowerShell.ControllerType]::IDE;
				ControllerNumber   = 0;
				ControllerLocation = 0
			}

			if ($Machine.Generation -eq 2)
			{
				$VMHDParams.ControllerType = [Microsoft.HyperV.PowerShell.ControllerType]::SCSI
			}

			$VHDPath = [String]::Empty
			try
			{
				$VHDPath = (Get-VMHardDiskDrive @VMHDParams).Path	
			}
			catch
			{
				Write-Warning ('VM "{0}" has no primary hard drive' -f $Machine.Name)
			}

            $DiskNum = (Mount-VHD -Path $VHDPath -Passthru).DiskNumber
            $DriveLetters = (Get-Disk $DiskNum | Get-Partition).DriveLetter
            if ((Get-Disk $DiskNum).OperationalStatus -ne 'Online')
            {
                Set-Disk $MountedVHD.Number -IsOffline:$false -IsReadOnly:$false
                Set-Disk -Number $DiskNum -IsOffline $false
                Set-Disk -Number $DiskNum -IsReadOnly $false
            }

            #Install the patch
            $TargetDriveLetter = ''
            foreach ($DriveLetter in $DriveLetters)
            {
                if (Test-Path ($DriveLetter + ':Windows'))
                {
                    $TargetDriveLetter = $DriveLetter
                }
            }

            if($DriveLetter)
            {
                foreach ($UpdateFile in $UpdateFiles)
                {
                    try
                    {
                        $OutNull = Add-WindowsPackage -PackagePath $UpdateFile -Path ($TargetDriveLetter + ':') -ErrorAction Stop	
                    }
                    catch
                    {
                        # Add-WindowsPackage writes to the warning and the error stream on errors so let its warning speak for itself
                        # Only include more information for an unnecessary patch
                        if ($_.Exception.ErrorCode -eq 0x800f081e)
                        {
                            Write-Warning 'This package is not applicable'
                        }
                    }
                }
            }
            else
            {
                Write-Error -Message ('No drive on VM {0} has a Windows folder' -f $Machine.Name) -ErrorAction Continue
            }

            Dismount-VHD -Path $VHDPath
		}
		else
		{
			Write-Warning -Message ('{0} cannot be updated because it is not in an Off state' -f $Machine.Name)
		}
	}
}

The script is fairly straight-forward, but its help does include a couple of examples to get you started.

Bug Reports and Enhancement Requests

This script is not very complicated and I did test the entire thing with all possible parameter settings. Let me know here or on the GitHub issues page.

If you need it to do something that it doesn’t, let me know. I won’t promise feature updates (especially if they’re complicated), but I’ll at least listen to requests. And, it’s open source, so you can add updates of your own.

I recently uploaded another free PowerShell script which detects problems with MAC address in Hyper-V which is proving very popular with you guys.

If you just can’t get enough, here are more free PowerShell scripts for you.

The post Free PowerShell Script for Hyper-V: Integration Services on Older Systems appeared first on Altaro DOJO | Hyper-V.

]]>
https://www.altaro.com/hyper-v/powershell-integration-services/feed/ 0
Free PowerShell Script for Hyper-V: Detect MAC Address Conflicts https://www.altaro.com/hyper-v/powershell-script-mac-address/ https://www.altaro.com/hyper-v/powershell-script-mac-address/#comments Thu, 20 Dec 2018 12:34:44 +0000 https://www.altaro.com/hyper-v/?p=17243 Quickly identify problems with MAC addresses in Hyper-V with this free PowerShell script plus instructions on how to use it

The post Free PowerShell Script for Hyper-V: Detect MAC Address Conflicts appeared first on Altaro DOJO | Hyper-V.

]]>

All physical network cards ship with a hard-coded unique identifier known as a media access control (MAC) address. These 48-bit identifiers help to ensure that Ethernet traffic finds its intended destination. If you want more information on that, follow our networking series. Here, I want to talk about the virtual MAC addresses that Hyper-V’s virtual network adapters use. I’ve got a script that can help you to quickly identify problems with MAC addresses in Hyper-V.

An Overview of Hyper-V MACs

Virtualization allows us to “fake” just about anything computer-related. Since virtual network adapters participate on Ethernet networks, they get “faked” MAC addresses. Each Hyper-V host generates a default pool. They all use the same first three octets: 00-15-5D, as Microsoft owns that prefix. The host generates the next two octets using an algorithm on its own physical hardware IDs. When virtual adapters start for the first time https://github.com/ejsiron/Posher-V/blob/master/Docs/Get-VMMacConflict.md and have no MAC, the host assigns them the next available number in the final octet.

Unfortunately, only having two octets available for uniqueness can quickly lead to duplicates. Reloaded systems will start over at 0 and might generate an octet set already in use. Virtual machines can move, so without some sort of external arbitration system, you can easily wind up with MAC collisions. Working only with symptoms, you might not even realize you have MAC problems at first. Detecting duplicate MACs can be a difficult process, especially when you don’t have tools built for the job.

Native Tools to Detect MAC Addresses

Hyper-V does not include any tool just for MAC duplicate detection. It does allow you to easily view all of the MACs on a host.

PowerShell code to see the virtual machine MACs:

Get-VMNetworkAdapter -VMName *

It will display all virtual adapters, their virtual machines, their MAC addresses, and some other information:

PowerShell code to see the virtual machine MACs

That only shows virtual machine information, though. For any virtual adapters that belong to the management operating system, use this:

Get-VMNetworkAdapter -ManagementOS

That won’t quite do it, though. You need one more cmdlet to see information for the host’s unbound physical adapters:

Get-NetAdapter

Now you have enough information to figure out if you have locally-conflicting addresses. You’ll have to figure out a way to collate and compare them all, though. And then you’d have to do it across all of your hosts. So, I did that for you.

Script Notes

A couple of points to note:

  • This is the final version of this script that I will post on this article. Any future updates will appear on its Github page.
  • This is my first script that uses the CIM cmdlets exclusively. I still use Windows PowerShell because it works perfectly well for me, but I believe that this script will also work with PowerShell Core.
  • Because I use CIM and not any add-in cmdlet packs (e.g. Hyper-V or VMM), this script will run on any PowerShell system. It can target remote systems. I tested against 2012 R2 and 2016 (simultaneously, even). It might work on 2012.
  • I built the script to look for collisions, not duplicates. MACs in distinct VLANs do not collide. Note these things:
    • You can use the -ExcludeVlans switch to ignore VLANs. That will uncover duplicate MACs no matter their VLAN.
    • The script cannot reliably detect VLAN membership of physical adapters. It will treat them as members of the untagged VLAN.
    • During testing, I discovered that a host’s virtual adapter has the same MAC as a virtual machine’s adapter on the same switch, it will cause collisions even if they do not share a VLAN. Due to the script’s architecture, that’s a difficult thing to capture using defaults. Use the -ExcludeVlans switch for now.
  • The script does work against vNICs in private VLANs and trunking vNICs. Each combination will appear as PrimaryVLAN:SecondaryVLAN, ex: 2:5.
  • The script has full help, including examples. Use the -Online switch to access a browser-friendly version of help.
  • If the script outputs nothing, that means that it didn’t find any duplicates.
  • You can pipe the output to Out-GridView or any of the CSV or HTML cmdlets.

Script File Listing

The text of the script’s initial release appears below:

<# .SYNOPSIS Locate conflicting Hyper-V virtual network adapter MAC addresses. .DESCRIPTION Locate conflicting Hyper-V virtual network adapter MAC addresses. With default settings, will scan the indicated hosts and generate a report of all adapters, virtual and physical, that use the same MAC in the same VLAN. Skips physical adapters bound by a virtual switch or team as these generate false positives. .PARAMETER ComputerName Name of one or more hosts running Hyper-V. If -HostFile is also set, uses both sources. If neither is set, uses the local system. .PARAMETER ExcludeHost If set, will not examine host MAC addresses for conflicts. .PARAMETER ExcludeVlan If set, will treat identical MAC addresses in distinct subnets as conflicts. .PARAMETER IncludeAllZero If set, will include virtual NICs with an all-zero MAC. .PARAMETER IncludeDisconnected If set, will include enabled but unplugged management operating system adapters. No effect if ExcludeHost is set. .PARAMETER IncludeDisabled If set, will include disabled management operating system adapters. No effect if ExcludeHost is set. .PARAMETER HostFile If provided, reads host names from the specified file. If -ComputerName is also set, uses both sources. If neither is set, uses the local system. .PARAMETER FileHasHeader If set, the first row in the file will be treated as a header row. If not set, the parser will assume the first column contains host names. Ignored if HostFile is not specified. .PARAMETER HeaderColumn If HostFile is a delimited type, use this to indicate which column contains the host names. If -HeaderColumn is set, but -FileHeader is NOT set, then this value will be treated as a column header AND a host name. If not set and the file is delimited, the first column will be used. Ignored if HostFile is not specified. .PARAMETER Delimiter The parser will treat this character as the delimiter in -HostFile. Defaults to the separator defined in the local machine's current culture. Ignored if HostFile is not specified. .NOTES Author: Eric Siron Version 1.0a, December 7, 2018 Released under MIT license .INPUTS String[] .EXAMPLE PS C:> Get-VMMacConflict
Checks the local machine for duplicate Hyper-V virtual machine MAC addresses. Includes active host adapters.
.EXAMPLE
PS C:> Get-VMMacConflict -ComputerName svhv1
Checks the Hyper-V system named "svhv1" for duplicate Hyper-V virtual machine MAC addresses. Includes active host adapters.
.EXAMPLE
PS C:> Get-VMMacConflict -ComputerName svhv1, svhv2, svhv3, svhv4
Checks all of the named Hyper-V systems for duplicate Hyper-V virtual machine MAC addresses. Includes active host adapters.
.EXAMPLE
PS C:> Get-VMMacConflict -HostFile C:hostnames.txt
Reads host names from C:hostnames.txt; it must be a single-column file of host names or all host names must be in the first column. VMs on these hosts are scanned for duplicate MAC addresses.
.EXAMPLE
PS C:> Get-VMMacConflict -HostFile C:hostnames.txt -FileHasHeader -HeaderColumn HostName
Reads host names from C:hostnames.txt; host names must be in a column named "HostName". VMs on these hosts are scanned for duplicate MAC addresses.
.EXAMPLE
PS C:> Get-VMMacConflict -HostFile C:hostnames.txt -HeaderColumn svhv1
Reads host names from C:hostnames.txt; looks for host names in a header-less column starting with svhv1. VMs on these hosts are scanned for duplicate MAC addresses.
.EXAMPLE
PS C:> Get-VMMacConflict -ExcludeVlan
Checks the local machine for duplicate Hyper-V virtual machine MAC addresses, even if they are in distinct VLANs. Includes active host adapters.
.EXAMPLE
PS C:> Get-VMMacConflict -IncludeDisconnected -IncludeDisabled
Checks the local machine for duplicate Hyper-V virtual machine MAC addresses. Includes active host adapters, even if they are disconnected or disabled.
.LINK
https://github.com/ejsiron/Posher-V/blob/master/Docs/Get-VMMacConflict.md
#>
[CmdletBinding()]
[OutputType([psobject[]])]
param
(
	[Parameter(ValueFromPipeline = $true, Position = 1)][String[]]$ComputerName = [String]::Empty,
	[Parameter()][Switch]$ExcludeHost,
	[Parameter()][Switch]$ExcludeVlan,
	[Parameter()][Switch]$IncludeAllZero,
	[Parameter()][Switch]$IncludeDisconnected,
	[Parameter()][Switch]$IncludeDisabled,
	[Parameter()][String]$HostFile = [String]::Empty,
	[Parameter()][Switch]$FileHasHeader,
	[Parameter()][String]$HeaderColumn = [String]::Empty,
	[Parameter()][Char]$Delimiter = (Get-Culture).TextInfo.ListSeparator
)

begin
{
	Set-StrictMode -Off	# script uses .Count to determine if an item is a collection and sometimes passes empty parameters intentionally
	$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Continue	# ensure that, even if errors occur, PSDefaultParameters is reset
	$ExistingDefaultParams = $PSDefaultParameterValues.Clone()
	$PSDefaultParameterValues['Get-CimInstance:Namespace'] = 'root/virtualization/v2'
	$PathToHostSwitchPort = 'Msvm_LANEndpoint/Msvm_LANEndpoint/Msvm_EthernetSwitchPort'
	$PathToHostVlanSettings = 'Msvm_EthernetPortAllocationSettingData/Msvm_EthernetSwitchPortVlanSettingData'

	$MacList = New-Object -TypeName System.Collections.ArrayList

	$SuppliedHostNames = New-Object -TypeName System.Collections.ArrayList
	$VerifiedHostNames = New-Object -TypeName System.Collections.ArrayList
	$ProcessedHostNames = New-Object -TypeName System.Collections.ArrayList
	if (-not $ComputerName -and [String]::IsNullOrEmpty($HostFile))
	{
		$OutNull = $SuppliedHostNames.Add($env:COMPUTERNAME)
	}

	if ($HostFile)
	{
		Write-Verbose -Message ('Importing host names from "{0}"' -f $HostFile)
		$HostListFile = (Resolve-Path -Path $HostFile).Path
		$FileData = Import-Csv -Path $HostFile -Delimiter $Delimiter
		if ($FileData)
		{
			if ([String]::IsNullOrEmpty($HeaderColumn))
			{
				$HeaderColumn = ($FileData | Get-Member -MemberType NoteProperty)[0].Name
			}
			if ($FileHasHeader.ToBool() -eq $false)
			{
				$OutNull = $SuppliedHostNames.Add($HeaderColumn)	# Import-CSV ALWAYS treats line 1 as a header
			}
			$SuppliedHostNames.AddRange($FileData.$HeaderColumn)
		}
	}

	function Get-CimPathedAssociation
	{
		param
		(
			[Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true)][Microsoft.Management.Infrastructure.CimInstance]$CimInstance,
			[Parameter(Mandatory = $true, Position = 2)][String]$PathToInstance,
			[Parameter()][Switch]$KeyOnly
		)
		$PathNodes = $PathToInstance.Split('/')
		$SearchInstances = New-Object System.Collections.ArrayList
		$OutNull = $SearchInstances.Add($CimInstance)
		for ($i = 0; $i -lt $PathNodes.Length; $i++)
		{
			$ChildCounter = 1
			if ($SearchInstances.Count)
			{
				$OnlyKeys = [bool]($KeyOnly -or $i -ne ($PathNodes.Count - 1))
				$TemporarySearchInstances = New-Object System.Collections.ArrayList
				foreach ($SearchInstance in $SearchInstances)
				{
					Write-Progress -Id 2 -Activity 'Querying CIM instances' -Status ('At distance {0} of {1}' -f ($i + 1), $PathNodes.Count) -CurrentOperation ('Loading {0} instances related to {1}' -f $SearchInstances.Count, $SearchInstance.CimClass.CimClassName) -PercentComplete (($ChildCounter++) / $SearchInstances.Count * 100)
					$AssociatedInstances = Get-CimAssociatedInstance -InputObject $SearchInstance -ResultClassName $PathNodes[$i]
					if ($AssociatedInstances)
					{
						if ($AssociatedInstances.Count)
						{
							$OutNull = $TemporarySearchInstances.AddRange($AssociatedInstances)
						}
						else
						{
							$OutNull = $TemporarySearchInstances.Add($AssociatedInstances)
						}
					}
				}
				Write-Progress -Id 2 -Activity 'Querying CIM instances' -Completed
				if ($TemporarySearchInstances.Count)
				{
					$SearchInstances = $TemporarySearchInstances
				}
				else
				{
					$SearchInstances.Clear()
				}
			}
		}
		$SearchInstances
	}

	function New-MacReportItem
	{
		param
		(
			[Parameter(Mandatory = $true)][String]$MacAddress,
			[Parameter()][String]$VMName = [String]::Empty,
			[Parameter()][String]$VmID = [String]::Empty,
			[Parameter(Mandatory = $true)][String]$ComputerName,
			[Parameter(Mandatory = $true)][String]$AdapterName,
			[Parameter(Mandatory = $true)][String]$AdapterID,
			[Parameter()][bool]$IsStatic = $false,
			[Parameter()][String]$SwitchName = [String]::Empty,
			[Parameter()][String]$VlanInfo
		)
		$MacReportItem = New-Object psobject
		$MacReportItemNoteProperties = [ordered]@{
			VMName       = $VMName;
			VmID         = $VmID;
			ComputerName = $ComputerName;
			AdapterName  = $AdapterName;
			AdapterID    = $AdapterID;
			MacAddress   = $MacAddress;
			IsStatic     = $IsStatic;
			SwitchName   = $SwitchName;
			Vlan         = $VlanInfo
		}
		$AddMemberInvariants = @{
			InputObject         = $MacReportItem;
			NotePropertyMembers = $MacReportItemNoteProperties;
		}
		$OutNull = Add-Member @AddMemberInvariants
		$MacReportItem
	}

	function Get-VlanInfoArray
	{
		param
		(
			[Parameter()][Microsoft.Management.Infrastructure.CimInstance]$VlanInfo
		)
		$VlanInfoArray = New-Object -TypeName System.Collections.ArrayList
		$OutNull = $VlanInfoArray.Add('ph')	# prevent PS from decaying the arraylist
		if ($VlanInfo)
		{
			switch ($VlanInfo.OperationMode)
			{
				2
				{
					# Trunk
					$OutNull = $VlanInfoArray.Add($VlanInfo.NativeVlanId)
					foreach ($VlanId in $VlanInfo.TrunkVlanIdArray)
					{
						if ($VlanId -ne $VlanInfo.NativeVlanId)
						{
							$OutNull = $VlanInfoArray.Add($VlanId)
						}
					}
				}
				3
				{
					# Private
					if ($VlanInfo.PvlanMode -eq 3)	# promiscuous; allows multiple secondaries
					{
						foreach ($SecondaryVlan in $VlanInfo.SecondaryVlanIdArray)
						{
							$OutNull = $VlanInfoArray.Add("$($VlanInfo.PrimaryVlanId):$SecondaryVlan")
						}
					}
					else	# community & isolated; one secondary
					{
						$OutNull = $VlanInfoArray.Add("${$VlanInfo.PrimaryVlanId}:${$VlanInfo.SecondaryVlanId}")
					}
				}
				default # 1 is access mode; 0 should never occur but if it does, treat it as access
				{
					$OutNull = $VlanInfoArray.Add($VlanInfo.AccessVlanId)
				}
			}
		}
		else
		{
			$OutNull = $VlanInfoArray.Add("0")
		}
		$VlanInfoArray
	}

	function IsDuplicate
	{
		param(
			[Parameter(Mandatory = $true)][psobject]$Left,
			[Parameter(Mandatory = $true)][psobject]$Right,
			[Parameter()][bool]$ExcludeVlan
		)
		[bool](
			$Left.MacAddress -eq $Right.MacAddress -and
			(
				$Left.ComputerName -ne $Right.ComputerName -or
				$Left.VmID -ne $Right.VmID -or
				$Left.AdapterID -ne $Right.AdapterID
			) -and
			($ExcludeVlan -or $Left.Vlan -eq $Right.Vlan)
		)
	}
}

process
{
	foreach ($HostName in $ComputerName)
	{
		if (-not $SuppliedHostNames.Contains($HostName))
		{
			$OutNull = $SuppliedHostNames.Add($HostName)
		}
	}

	if ($SuppliedHostNames.Count)
	{
		$Activity = 'Verifying hosts lists'
		foreach ($HostName in $SuppliedHostNames)
		{
			if (-not $HostName)
			{
				continue
			}
			Write-Progress -Activity $Activity -Status $HostName
			if (-not $VerifiedHostNames.Contains($HostName))
			{
				$DiscoveredName = $HostName
				try
				{
					$DiscoveredName = (Get-CimInstance -ComputerName $HostName -Namespace 'root/cimv2' -ClassName 'Win32_ComputerSystem' -ErrorAction Stop).Name
					if (-not $VerifiedHostNames.Contains($HostName))
					{
						$OutNull = $VerifiedHostNames.Add($DiscoveredName)
					}
				}
				catch
				{
					Write-Error -Exception $_.Exception -ErrorAction Continue
					continue
				}
			}
		}
		Write-Progress -Activity $Activity -Completed

		$Activity = 'Discovering MAC addresses'
		foreach ($HostName in $VerifiedHostNames)
		{
			if ($ProcessedHostNames.Contains($HostName))
			{
				continue
			}
			else
			{
				$OutNull = $ProcessedHostNames.Add($HostName)
			}

			$Session = $null
			try
			{
				Write-Progress -Activity $Activity -Status ('Connecting to host "{0}"' -f $HostName)
				$Session = New-CimSession -ComputerName $HostName -ErrorAction Stop
			}
			catch
			{
				Write-Warning -Message ('Cannot connect to {0}' -f $HostName)
				Write-Error -Exception $_.Exception -ErrorAction Continue
				continue
			}

			foreach ($VM in Get-CimInstance -CimSession $Session -ClassName Msvm_ComputerSystem)
			{
				$CurrentOperation = 'Querying {0}' -f $VM.ElementName
				if ($HostName -eq $VM.Name)
				{
					if (-not $ExcludeHost)
					{
						Write-Progress -Activity $Activity -Status 'Loading host adapters' -CurrentOperation $CurrentOperation
						$AdapterList = New-Object System.Collections.ArrayList

						$ExternalPorts = Get-CimAssociatedInstance -InputObject $VM -ResultClassName Msvm_ExternalEthernetPort -ErrorAction SilentlyContinue
						$InternalPorts = Get-CimAssociatedInstance -InputObject $VM -ResultClassName Msvm_InternalEthernetPort -ErrorAction SilentlyContinue

						if ($ExternalPorts)
						{
							if ($ExternalPorts.Count) { $AdapterList.AddRange($ExternalPorts) }
							else { $OutNull = $AdapterList.Add($ExternalPorts) }
						}
						if ($InternalPorts)
						{
							if ($InternalPorts.Count) { $AdapterList.AddRange($InternalPorts) }
							else { $OutNull = $AdapterList.Add($InternalPorts) }
						}

						foreach ($Adapter in $AdapterList)
						{
							if ($Adapter.IsBound)
							{
								continue
							}
							$TargetDeviceId = $null
							if ($Adapter.DeviceId -match '{.*}')
							{
								$TargetDeviceId = $Matches[0]
							}
							$VLAN = 0
							$SwitchName = [String]::Empty
							Write-Progress -Activity $Activity -Status 'Loading host adapter information' -CurrentOperation $CurrentOperation
							$MSAdapter = Get-CimInstance -CimSession $Session -Namespace root/StandardCimv2 -ClassName MSFT_NetAdapter -Filter ('DeviceId="{0}"' -f $TargetDeviceId)
							$AdapterID = $TargetDeviceId
							$Enabled = $MSAdapter.State -eq 2 -or $IncludeDisabled
							$Connected = $MSAdapter.MediaConnectState -eq 1 -or ($IncludeDisconnected -or ($MSAdapter.State -ne 2 -and $IncludeDisabled))
							if ($Enabled -and $Connected)
							{
								if ($Adapter.CimClass.CimClassName -eq 'Msvm_InternalEthernetPort')
								{
									Write-Progress -Activity $Activity -Status 'Loading host adapter switch information' -CurrentOperation $CurrentOperation
									$SwitchPort = Get-CimPathedAssociation -CimInstance $Adapter -PathToInstance $PathToHostSwitchPort
									if ($SwitchPort)
									{
										$AdapterID = ('Microsoft:{0}{1}' -f $SwitchPort.SystemName, $SwitchPort.Name)
										$VMSwitch = Get-CimAssociatedInstance -InputObject $SwitchPort -ResultClassName Msvm_VirtualEthernetSwitch
										if ($VMSwitch)
										{
											$SwitchName = $VMSwitch.ElementName
										}
										$VlanSettings = Get-CimPathedAssociation -CimInstance $SwitchPort -PathToInstance $PathToHostVlanSettings
										if ($VlanSettings)
										{
											$VLAN = $VlanSettings.AccessVlanId
										}
										else
										{
											$VLAN = $MSAdapter.VlanID
										}
									}
								}
								$OutNull = $MacList.Add((New-MacReportItem -MacAddress $Adapter.PermanentAddress -ComputerName $HostName -AdapterName $MSAdapter.Name -AdapterID $AdapterID -IsStatic $true -SwitchName $SwitchName -Vlan $VLAN))
							}
						}
					}
				}
				else
				{
					Write-Progress -Activity $Activity -Status 'Loading virtual machine settings' -CurrentOperation $CurrentOperation
					$VMSettings = Get-CimAssociatedInstance -InputObject $VM -ResultClassName Msvm_VirtualSystemSettingData | where -Property VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized'
					if ($VMSettings)
					{
						Write-Progress -Activity $Activity -Status 'Loading virtual machine vNIC settings' -CurrentOperation $CurrentOperation
						foreach ($EthPortSettings in Get-CimAssociatedInstance -InputObject $VMSettings -ResultClassName Msvm_EthernetPortAllocationSettingData)
						{
							$VMSwitchName = [String]::Empty
							if ($EthPortSettings.EnabledState -eq 2)
							{
								$VMSwitchName = $EthPortSettings.LastKnownSwitchName
							}
							$VNICPortSettings = Get-CimAssociatedInstance -InputObject $EthPortSettings -ResultClassName Msvm_SyntheticEthernetPortSettingData
							if (-not $VNICPortSettings)
							{
								$VNICPortSettings = Get-CimAssociatedInstance -InputObject $EthPortSettings -ResultClassName Msvm_EmulatedEthernetPortSettingData
							}

							if ($VNICPortSettings -and ($IncludeAllZero -or $VNICPortSettings.Address -ne '0' * 12))
							{
								$VlanSettings = Get-CimAssociatedInstance -InputObject $EthPortSettings -ResultClassName Msvm_EthernetSwitchPortVlanSettingData

								foreach ($VlanSet in (Get-VlanInfoArray $VlanSettings | where { $_ -ne 'ph'}))
								{
									$OutNull = $MacList.Add((New-MacReportItem -MacAddress $VNICPortSettings.Address -VMName $VM.ElementName -VmID $VM.Name -ComputerName $HostName -AdapterName $VNICPortSettings.ElementName -AdapterID $VNICPortSettings.InstanceID -IsStatic $VNICPortSettings.StaticMacAddress -VlanInfo $VlanSet -SwitchName $VMSwitchName))
								}
							}
						}
					}
				}
			}
			$Session.Close()
		}
		Write-Progress -Activity $Activity -Completed
	}
}

end
{
	$Duplicates = New-Object -TypeName System.Collections.ArrayList
	foreach ($OuterItem in $MacList)
	{
		foreach ($InnerItem in $MacList)
		{
			if (-not $Duplicates.Contains($InnerItem))
			{
				if (IsDuplicate -Left $InnerItem -Right $OuterItem -ExcludeVlan $ExcludeVlan.ToBool())
				{
					$OutNull = $Duplicates.Add($InnerItem)
				}
			}

		}
	}

	$Duplicates.ToArray()

	$PSDefaultParameterValues.Clear()
	foreach ($ParamKey in $ExistingDefaultParams.Keys)
	{
		$PSDefaultParameterValues.Add($ParamKey, $ExistingDefaultParams[$ParamKey])
	}
}

I Need Your Help!

I did test this, but the more eyes looking at it, the better. If you find any problems, share them here or use the GitHub’s issues feature to file a bug report.

Which PowerShell Script would you like?

Is there a particular PowerShell script of a problem you think would be solved using PowerShell you’d like to know about? Let me know in the comments below or head on over to the Altaro Dojo Forums and open a new thread. I’m active in the Dojo Forums community and will gladly answer your PowerShell questions there – who knows I might already have that PowerShell script you need!

The post Free PowerShell Script for Hyper-V: Detect MAC Address Conflicts appeared first on Altaro DOJO | Hyper-V.

]]>
https://www.altaro.com/hyper-v/powershell-script-mac-address/feed/ 5
Why You Should Be Compacting Your Hyper-V Virtual Disks https://www.altaro.com/hyper-v/compact-hyper-v-virtual-disks-vhdx/ https://www.altaro.com/hyper-v/compact-hyper-v-virtual-disks-vhdx/#comments Tue, 20 Feb 2018 16:31:16 +0000 https://www.altaro.com/hyper-v/?p=16074 The reasons, prerequisites, and steps necessary to reclaim unused space from your dynamically-expanding VHDXs. Includes PowerShell Tool for Compacting VHDX Files.

The post Why You Should Be Compacting Your Hyper-V Virtual Disks appeared first on Altaro DOJO | Hyper-V.

]]>

Hyper-V’s dynamically expanding virtual hard disks (VHD/VHDX) provide numerous benefits. I suspect that most of us use them to achieve efficient use of physical storage. However, their convenience does come with a maintenance cost. They grow as needed, but they will never release any space unless you manually intervene (it could be scripted if you so desire). In this article, we’ll cover the reasons, prerequisites, and steps necessary to reclaim unused space from your dynamically-expanding VHDXs.

“Compact” or “Shrink” Hyper-V Virtual Hard Disks?

Sometimes definitions for “compact” and “shrink” get crossed when talking about VHDXs. They do not mean the same thing and you use completely different operations to achieve them.

Every virtual hard disk type (fixed, dynamically-expanding, and differencing) has a fixed upper size limit. “Shrinking” a VHDX reduces that limit. Any of the types can be shrunk. “Compacting” does not change the upper limit at all. Instead, it reduces the physical disk space consumed by a dynamically-expanding or differencing VHDX by removing empty blocks. I’ll explain what a “block” means to these VHDXs in the next section. You can run a compact operation on any of the types, but it will never have any effect on a fixed VHDX (the graphical interface screens will not even allow you to try). For more information on the shrink process, refer to our earlier article on the issue.

What Does “Compact” Mean for Hyper-V Virtual Hard Disks

Dynamically-expanding VHDXs function is a straightforward process. When created, each has a specific, permanently-set “block size”. When a guest operating system or some external action attempts to write information into the VHDX, the VHDX driver first checks to see if the file contains sufficient space in unclaimed blocks for that data. If it does, then the write proceeds. If it does not, the VHDX driver expands the physical file by the minimum number of blocks needed to contain the write.

However, when a process deletes a file (or uses some other technique to clear data), the VHDX driver does not do anything with the block. Most importantly, it does not automatically remove empty blocks from the file. I’ve read a fair number of complaints on that, but they lack merit. To maintain such a thing would be inherently dangerous and prohibitively expensive in terms of IOPS.

Instead, we manually initiate the compact process. The system scans the data region of the VHDX, looking for completely empty blocks. When it finds one, it removes it from the file. I’m not aware of the precise mechanism that it uses, but I suspect that it simply finds the next used block and shifts it backward, eventually lopping off the end of the file.

compact-blockview

Why Should I Compact Hyper-V Virtual Hard Disks?

Physical systems rarely use up all of their disk space. The same can be said for virtual systems. Therein lies the primary reason to use dynamically-expanding virtual hard disks. I especially like them for VHDXs that contain operating systems when you’ll be placing data on different VHDXs. I’ve got some VHDXs nearing five years of age that have a 60GB maximum but still use only around 25GB on disk. With many OS VHDXs, that quickly adds up to a great deal of saved storage. That’s only one example, though; I generally use dynamically-expanding VHDX in any case where I know it won’t cause problems (high-churn database systems would be a poor usage for them).

However, sometimes operations occur that consume an inordinate amount of disk space. Sometimes space is just used and there’s nothing you can do about it. Other times, the usage is only temporary. For example, you might copy several ISO files to the C: drive of a guest in order to perform a local software installation. If you’re doing in-place operating system upgrades, they’ll make a duplicate of the existing installation so that you can perform a rollback. A system may begin life with an oversized swap file that you later move to a different VHDX, leaving behind gigabytes of unused space. I always compact VHDXs with sysprepped operating systems that I will use as templates.

Why Should I NOT Compact Hyper-V Virtual Hard Disks?

“Premature optimization is the root of all evil.” — Donald Knuth

I often complain about administrators that arbitrarily guess that their disk performance needs will be so great that anything other than fixed (or, egads, pass-through) is unacceptable. At the other end of the spectrum, some administrators became agitated when their VHDXs consume even a few gigabytes above their minimum. Most dynamically-expanding VHDXs will eventually find some sort of “normal state” in which they can re-use space from deleted data (example: Windows Updates).

I can’t give you a fine description of when a compact operation becomes viable, but do not use it to try to squeeze every last drop of space out of your system. You’ll eventually lose that battle one way or another. Do not compact over minor gains. Do not compact VHDXs that will never find any reasonable steady state (such as file servers). Do not compact as part of some routine maintenance cycle. If you have set up an automated script because your systems constantly approach the hard limits of your storage, spend the money to expand your storage. Compacting requires downtime and equipment wear, so it’s not free.

How Do I Use PowerShell to Compact a VHDX?

You can compact using GUI tools, but PowerShell provides the complete method. Remember two things:

  • You cannot compact a VHDX connected to a running virtual machine.
  • The computer system that runs the Optimize-VHD cmdlet must have the complete Hyper-V role installed. The PowerShell module alone does not contain the necessary system services. If you must run the compact operation on a system that does not have Hyper-V, you can use diskpart instead.

The process centers around the Optimize-VHD cmdlet. In general, you’ll want to mount the VHDX into the management operating system to get the best results. For example:

Mount-VHD .dyndisk.vhdx -ReadOnly
Optimize-VHD .dyndisk.vhdx -Mode Full
Dismount-VHD .dyndisk.vhdx

Be aware that even for very small files, that can take some time to complete. The amount of time depends mostly on your available CPU cycle and the speed of your disk subsystem.

A PowerShell Tool for Compacting VHDX Files

You can easily convert the above three lines into a re-usable utility:

function Compact-VHD
{
	<#
	.SYNOPSIS
	Fully optimizes a Hyper-V VHD/X file.
	
	.DESCRIPTION
	Mounts the target VHD/X file as read-only, performs full optimization, and then dismounts it.
	
	.NOTES
	v1.0 January 28th, 2018
	(c) 2018 Eric Siron
	#>
	#requires -Module Hyper-V
	[CmdletBinding()]
	param(
		[Parameter(Mandatory = $true, ValueFromPipeline = $true)][String]$Path,
		[Parameter()][Microsoft.Vhd.PowerShell.VhdCompactMode]$Mode = [Microsoft.Vhd.PowerShell.VhdCompactMode]::Full
	)
	try
 {
		$Path = (Resolve-Path -Path $Path -ErrorAction Stop).Path
		if ($Path -notmatch '.a?vhdx?$') { throw }
	}
	catch
 {
		throw('{0} is not a valid VHDX file.' -f $Path)
	}
	Mount-VHD -Path $Path -ReadOnly -ErrorAction Stop
	Optimize-VHD -Path $Path -Mode $Mode -ErrorAction Continue
	Dismount-VHD -Path $Path
}

You can dot-source that or add it to your PowerShell profile to use it anytime.

What Do the Different VHDX Compact Modes Mean?

If you read the help or tab through the options for the -Full parameter, you’ll see several choices. I feel like the built-in help describes it well enough, but more than a few people have gotten lost in terms with similar meanings. A bit of clarity:

  • An empty block means a continuous length of space within the VHDX that its header has designated as a block and contains all zeros. A single 1 anywhere in the entire block means that the VHDX driver will treat it as used.
  • An unused block may have some 1s, but the guest operating system’s file system has marked the space as not being used. This is common after file deletion; few operating systems will actually remove the bits from the disk. They simply mark it as unused in their file table(s). Two points on that:
    • At this time, the VHDX driver works most reliably with the NTFS file system. I have not yet tested for ReFS. It does not recognize any Linux file systems. In order for the system to best detect unused blocks, the VHDX must be mounted in read-only mode.
    • The VHDX driver also recognizes the Trim and Unmap commands. That allows it to work with your hardware to determine what blocks are unused.

 

Hopefully, that helps smooth out explanations of the modes:

  • Full: Full optimization requires the most time and resources. It will take out both empty and unused blocks. In order to detect unused blocks, the VHDX must be mounted in read-only mode. If the VHDX isn’t mounted, then it won’t be able to find empty blocks as easily (assume that it won’t find them at all).
  • Quick: The system only looks for unused blocks using the contained file system’s metadata. If you don’t mount the VHDX first, nothing will change.
  • Pretrimmed: Utilizes information from the trim/unmap commands to detect unused blocks. Does not look for empty blocks and does not query the contained file system for unused blocks.
  • Prezeroed: If the VHDX driver intercepted zero writes for existing blocks (such as from tools like sdelete, then it has already recorded in its own tables that they’re empty. Use this mode to remove only those blocks.
  • Retrim: Retrim reads from the VHDX file’s metadata for blocks marked as empty and reports them as trim or unmap commands to the underlying hardware.

Only the Full mode directly scans the file for empty blocks. The others work only with the VHDX and/or guest file system metadata.

How Do I Use Hyper-V Manager to Compact a VHDX?

Hyper-V Manager can compact a disconnected VHD/X or one attached to a powered-off virtual machine. It does not perform a very thorough job, though. I do not know which method it uses, but you’ll discover very quickly that it is not the equivalent of PowerShell’s “Full”. Whatever technique it uses, it will not automatically mount the VHDX.

You can access the Edit function directly on a VM’s disk property page or by going to the Edit Disk link in the main interface. Use the main interface for VHDXs that have no owning virtual machine.

compact-hvmstarts

Both ways invoke the same wizard.

  1. On the first screen, you select the target VHDX file. If you started from a VM’s property page, then the Location line is filled in and grayed out.
    compact-browse
  2. Next, the wizard will ask what you want to do with the disk. Choose Compact. If the Compact option does not appear, the virtual machine is turned on or you selected a fixed disk.
    compact-opselect
  3. Click Finish on the final screen. You’ll get a small dialog that shows the progress.
    compact-finishwiz

Can I Compact a VHDX that has Differencing Children?

The system will not prevent you from compacting a VHDX that has children unless one of the children is in use. If you manage to compact the parent VHDX, you will break the parent-child relationship.

What About VHDXs that Contain Non-Microsoft File Systems?

The Full mode will detect fully empty blocks, but cannot interact with any non-Microsoft file system metadata to locate unused blocks. However, most modern filesystems now support trim/unmap. If you can perform that operation within the guest operating system, you can then perform a Full optimize pass to shrink the VHDX. I wrote an article on compacting VHDXs with Linux filesystems that have more information.

Why Does the Compact Operation Save Nothing or Very Little?

The simple answer is that the blocks are not empty. If a block contains a single non-zero bit, then the entire block must be kept. You cannot see the VHDX’s blocks from within the guest. Short of some sort of file scanner, I don’t know of any way to view the blocks aside from a hex editor.

Some tips on cleaning up space:

  • Delete as many files as possible
  • Clear the guest’s Recycle Bin(s)
  • Use utilities that write zeros to free space
  • Use a defragmentation tool that will consolidate free space

If you’re really stuck, you can use a tool like robocopy or rsync to move the files into a completely new VHDX.

What Block Sizes are Valid for a Dynamically-Expanding VHDX?

By default, all VHDXs will have a 32 megabyte block size. You can only change that at creation time when using New-VHD. Set the BlockSizeBytes parameter as desired. Unfortunately, they don’t list possible values so it’s a bit of trial and error. I tried 1MB, 8MB, 16MB, 64MB, and 256MB sizes with success, in addition to the default 32MB. 512 bytes failed, as did 512MB.

How Can I Find a Dynamically-Expanding VHDX’s Block Size?

Only PowerShell’s Get-VHD can show you the block size.

compact-showblocksize

 

What Block Size Should I Use?

You have a basic trade-off: small block sizes result in more writes with less unused space. Larger block sizes result in fewer writes with potentially more unused space.

With small block sizes, scattered small files will be less likely to result in a VHDX with a great many almost-but-not-quite empty blocks. For the various ext* filesystems found on Linux, a 1MB block size can greatly reduce your VHDX sizes. The greatest downside is that you might get a lot of fragmentation of the physical VHDX. Then again, you might not. It depends on how the writes occur. Infrequent small writes will result in more fragmentation. If you use robust, modern storage, then fragmentation is not usually a major concern.

Large block sizes don’t have much to offer. If you were to store a rarely-accessed archival database in a dynamically-expanding VHDX with block sizes that match your guest OS’s allocation unit size, you might, in theory, see something improve. Probably not, though.

I recommend that you stick to the default 32mb block size for any guest that doesn’t use an ext filesystem. Use 1MB block sizes for those, if you remember.

The post Why You Should Be Compacting Your Hyper-V Virtual Disks appeared first on Altaro DOJO | Hyper-V.

]]>
https://www.altaro.com/hyper-v/compact-hyper-v-virtual-disks-vhdx/feed/ 4
Free Hyper-V Script: Virtual Machine Storage Consistency Diagnosis https://www.altaro.com/hyper-v/free-script-vm-storage-diagnosis/ https://www.altaro.com/hyper-v/free-script-vm-storage-diagnosis/#comments Tue, 12 Dec 2017 17:32:38 +0000 https://www.altaro.com/hyper-v/?p=13037 Free PowerShell script that identifies inconsistencies in the distribution of files on a virtual machine. Includes guide to use and associated parameters

The post Free Hyper-V Script: Virtual Machine Storage Consistency Diagnosis appeared first on Altaro DOJO | Hyper-V.

]]>

Hyper-V allows you to scatter a virtual machine’s files just about anywhere. That might be good; it might be bad. That all depends on your perspective and need. No good can come from losing track of your files’ sprawl, though. Additionally, the placement and migration tools don’t always work exactly as expected. Even if the tools work perfectly, sometimes administrators don’t. To help you sort out these difficulties, I’ve created a small PowerShell script that will report on the consistency of a virtual machine’s file placement.

Free Hyper-V Script from Altaro

How the Script Works

I designed the script to be quick and simple to use and understand. Under the hood, it does these things:

  1. Gathers the location of the virtual machine’s configuration files. It considers that location to be the root.
  2. Gathers the location of the virtual machine’s checkpoint files. It compares that to the root. If the locations aren’t the same, it marks the VM’s storage as inconsistent.
  3. Gathers the location of the virtual machine’s second-level paging files. It compares that to the root. If the locations aren’t the same, it marks the VM’s storage as inconsistent.
  4. Gathers the location of the virtual machine’s individual virtual hard disk files. It compares each to the root. If the locations aren’t the same, it marks the VM’s storage as inconsistent.
  5. Gathers the location of any CD or DVD image files attached to the virtual machine. It compares each to the root. If the locations aren’t the same, it marks the VM’s storage as inconsistent.
  6. Emits a custom object that contains:
    1. The name of the virtual machine
    2. The name of the virtual machine’s Hyper-V host
    3. The virtual machine’s ID (a string-formatted GUID)
    4. A boolean ($true or $false) value that indicates if the virtual machine’s storage is consistent
    5. An array that contains a record of each location. Each entry in the array is itself a custom object with these two pieces:
      1. The component name(Configuration, Checkpoints, etc.)
      2. The location of the component

It doesn’t check for pass-through. A VM with pass-through disks would have inconsistent storage placement by definition, but this script completely ignores pass-through.

The script doesn’t go through a lot of validation to check if storage locations are truly unique. If you have a VM using “\systemVMs” and “\systemVMFiles” but they’re both the same physical folder, the VM will be marked as inconsistent.

Script Usage

I built the script to be named Get-VMStorageConsistency. All documentation and examples are built around that name. You must supply it with a virtual machine. All other parameters are optional.

Use Get-Help Get-VMStorageConsistency -Full to see the built-in help.

Parameters:

  • VM (aliases “VMName” and “Name”): The virtual machine(s) to check. The input type is an array, so it will accept one or more of any of the following:
    • A string that contains the virtual machine’s names
    • A VirtualMachine object (as output from Get-VM, etc.)
    • A GUID object that contains the virtual machine’s ID
    • A WMI Msvm_ComputerSystem object
    • A WMI MSCluster_Resource object
  • ComputerName: The name of the host that owns the virtual machine. If not specified, defaults to the local system. Ignored if VM is anything other than a String or GUID. Only accepts strings.
  • DisksOnly: A switch parameter (include if you want to use it, leave off otherwise). If specified, the script only checks at the physical storage level for consistency. Examples:
    • With this switch, C:VMs and C:VMCheckpoints are the same thing (simplifies to C:)
    • With this switch, C:ClusterStorageVMs1VMFiles and C:ClusterStorageVMs1VHDXs are the same thing (simplifies to C:ClusterStorageVMs1)
    • With this switch, \storage1VMsVMFiles and \storage1VMsVHDFiles are the same thing (simplifies to \storage1VMs)
    • Without this switch, all of the above are treated as unique locations
  • IgnoreVHDFolder: A switch parameter (include if you want to use it, leave off otherwise). If specified, ignores the final “Virtual Hard Disks” path for virtual hard disks. Notes:
    • With this switch, VHDXs in “C:VMsVirtual Hard Disks” will be treated as though they were found in C:VMs
    • With this switch, VHDXs in “C:VMsVirtual Hard DisksVHDXs” will not be treated specially. This is because there is a folder underneath the one named “Virtual Hard Disks”.
    • With this switch, a VM configured to hold its checkpoints in a folder named “C:VMsVirtual Hard Disks” will not treat its checkpoints especially. This is because the switch only applies to virtual hard disk files.
  • Verbose: A built-in switch parameter. If specified, the script will use the Verbose output stream to show you the exact comparison that caused a virtual machine to be marked as inconsistent. Note: The script only traps the first item that causes a virtual machine to be inconsistent. That’s because the Consistent marker is a boolean; in boolean logic, it’s not possible to become more false. Therefore, I considered it to be wasteful to continue processing. However, all locations are stored in the Locations property of the report. You can use that for an accurate assessment of all the VMs’ component locations.

The Output Object

The following shows the output of the cmdlet run on my host with the -IgnoreVHDFolder and -Verbose switches set:

conscript_output

Output object structure:

  • Name: String that contains the virtual machine’s name
  • ComputerName: String that contains the name of the Hyper-V host that currently owns the virtual machine
  • VMId: String representation of the virtual machine’s GUID
  • Consistent: Boolean value that indicates whether or not the virtual machine’s storage is consistently placed
  • Location: An array of custom objects that contain information about the location of each component

Location object structure:

  • Component: A string that identifies the component. I made these strings up. Possible values:
    • Configuration: Location of the “Virtual Machines” folder that contains the VM’s definition files (.xml, .vmcx, .bin, etc.)
    • Checkpoints: Location of the “Snapshots” folder configured for this virtual machine. Note: That folder might not physically exist if the VM uses a non-default location and has never been checkpointed.
    • SecondLevelPaging: Location of the folder where the VM will place its .slp files if it ever uses second-level paging.
    • Virtual Hard Disk: Full path of the virtual hard disk.
    • CD/DVD Image: Full path of the attached ISO.

An example that puts the object to use:

Get-VM | .Get-VMStorageConsistency.ps1 -IgnoreVHDFolder | ? Consistent -ne $true | select Name

The above will output only the names of local virtual machines with inconsistent storage.

Script Source

The script is intended to be named “Get-VMStorageConsistency”. As shown, you would call its file (ex.: C:ScriptsGet-VMStorageConsistency). If you want to use it dot-sourced or in your profile, uncomment lines 55, 56, and 262 (subject to change from editing; look for the commented-out function { } delimiters).

<#
.SYNOPSIS
	Verifies that a virtual machine's files are all stored together.
.DESCRIPTION
	Verifies that a virtual machine's files are all stored together. Reports any inconsistencies in locations.
.PARAMETER VM
	The virtual machine to check.
	Accepts objects of type:
	* String: A name of a virtual machine.
	* VirtualMachine: An object from Get-VM
	* System.GUID: A virtual machine ID. MUST be of type System.GUID to match.
	* ManagementObject: A WMI object of type Msvm_ComputerSystem
	* ManagementObject: A WMI object of type MSCluster_Resource
.PARAMETER ComputerName
	The name of the computer that hosts the virtual machine to remove. If not specified, uses the local computer.
	Ignored if VM is of type VirtualMachine or ManagementObject.
.PARAMETER DisksOnly
	Set to true if you only care if data resides on different physical disks/LUNs.
	Otherwise, a VM will be marked inconsistent if components exist in different folders.
.PARAMETER IgnoreVHDFolder
	Set to true if you want to ignore the 'Virtual Hard Disks' subfolder for VHD/X files.
	Example: If set, then VHDXs in C:VMsVirtual Hard Disks will be treated as though they are in C:VMs
	Ignored when DisksOnly is set
.NOTES
	Author: Eric Siron
	Version 1.0
	Authored Date: October 2, 2017
.EXAMPLE
	Get-VMStorageConsistency -VM vm01
	
	Reports the consistency of storage for the virtual machine named "vm01" on the local host.

.EXAMPLE
	Get-VMStorageConsistency -VM vm01 -ComputerName hv01
	
	Reports the consistency of storage for the virtual machine named "vm01" on the host named "vm01".

.EXAMPLE
	Get-VM | Get-VMStorageConsistency
	
	Reports the consistency of storage for all local virtual machines.
.EXAMPLE
	Get-VMStorageConsistency -VM vm01 -DisksOnly
	
	Reports the consistency of storage for the virtual machine named "vm01" on the local host. Only checks that components reside on the same physical storage.
.EXAMPLE
	Get-VMStorageConsistency -VM vm01 -IgnoreVHDFolder
	
	Reports the consistency of storage for the virtual machine named "vm01" on the local host. If VHDXs reside in a Virtual Hard Disks subfolder, that will be ignored.
	So, if the VM's components are in \smbstoreVMs but the VHDXs are in \smbstoreVMsVirtual Hard Disks, the locations will be treated as consistent.
	However, if the VM's components are in \smbstoreVMsVirtual Machines while the VHDXs are in \smbstoreVMsVirtual Hard Disks, that will be inconsistent.
#>
#requires -Version 4

# function Get-VMStorageConsistency # Uncomment this line to use as a dot-sourced function or in a profile. Also next line and last line
#{ # Uncomment this line to use as a dot-sourced function or in a profile. Also preceding line and last line
[CmdletBinding()]
param(
	[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1)]
	[Alias('VMName', 'Name')]
	[Object[]]
	$VM,
	[Parameter(Position = 2)][String]$ComputerName = $env:COMPUTERNAME,
	[Parameter()][Switch]$DisksOnly,
	[Parameter()][Switch]$IgnoreVHDFolder
)
BEGIN {
	$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
	Set-StrictMode -Version Latest

	function New-LocationObject
	{
		<#
		.SYNOPSIS
		Defines/creates an object matching a VM's component to its location.
		#>
		$LocationObject = New-Object -TypeName psobject
		Add-Member -InputObject $LocationObject -MemberType NoteProperty -Name 'Component' -Value ([System.String]::Empty)
		Add-Member -InputObject $LocationObject -MemberType NoteProperty -Name 'Location' -Value ([System.String]::Empty)
		$LocationObject
	}
	function New-StorageConsistencyReport
	{
		<#
		.SYNOPSIS
		Defines/creates a VM's storage consistency report object.
		#>
		$Report = New-Object -TypeName psobject
		Add-Member -InputObject $Report -MemberType NoteProperty -Name 'Name' -Value ([System.String]::Empty)
		Add-Member -InputObject $Report -MemberType NoteProperty -Name 'ComputerName' -Value ([System.String]::Empty)
		Add-Member -InputObject $Report -MemberType NoteProperty -Name 'VMId' -Value ([System.String]::Empty)
		Add-Member -InputObject $Report -MemberType NoteProperty -Name 'Consistent' -Value $false
		Add-Member -InputObject $Report -MemberType NoteProperty -Name 'Locations' -Value @()
		$Report
	}

	function Parse-Location
	{
		<#
		.SYNOPSIS
		Extracts the location information from a component's path.
		.PARAMETER Path
		The path to parse
		.PARAMETER DisksOnly
		If specified, returns only the drive portion of the path. If a CSV is detected, returns the mount point name.
		.PARAMETER TrimFile
		If specified, assumes that Path includes a file name. Use with VHDXs and ISOs.
		.PARAMETER IgnoreVHDFolder
		If specified, will remove any trailing 'Virtual Hard Disks' subfolder
		#>
		param(
			[Parameter()][String]$Path,
			[Parameter()][bool]$DisksOnly,
			[Parameter()][bool]$TrimFile,
			[Parameter()][bool]$IgnoreVHDFolder
		)
		if ($DisksOnly)
		{
			if ($Path -match '([A-Za-z]:\ClusterStorage\.+?)(\|z)')
			{
				$Path = $Matches[1]
			}
			else
			{
				$Path = [System.IO.Path]::GetPathRoot($Path)
			}
		}
		else
		{
			if ($TrimFile)
			{
				$Path = [System.IO.Path]::GetDirectoryName($Path)
			}
			if ($IgnoreVHDFolder)
			{
				$Path = $Path -replace '\?Virtual Hard Disks\?$', ''
			}
		}
		$Path -replace '\$', ''
	}

	function Process-Location
	{
		param(
			[Parameter()][ref]$Report,
			[Parameter()][String]$Component,
			[Parameter()][String]$Location,
			[Parameter()][bool]$DisksOnly,
			[Parameter()][String]$RootLocation,
			[Parameter()][bool]$TrimFile = $false,
			[Parameter()][bool]$IgnoreVHDFolder = $false
		)
		$ThisLocation = New-LocationObject
		$ThisLocation.Component = $Component
		$ThisLocation.Location = $Location
		$Report.Value.Locations += $ThisLocation
		$CurrentObservedLocation = Parse-Location -Path $Location -DisksOnly $DisksOnly -TrimFile $TrimFile -IgnoreVHDFolder $IgnoreVHDFolder
		if ($Report.Value.Consistent)
		{
			if ($CurrentObservedLocation -ne $RootLocation)
			{
				$Report.Value.Consistent = $false
				Write-Verbose -Message ("VM {0} on {1} failed consistency on component {2}.`r`n`tRoot component location: {3}`r`n`t{2} location: {4}" -f $Report.Value.Name, $Report.Value.ComputerName, $Component, $RootLocation, $CurrentObservedLocation)
			}
		}
	}
}
PROCESS {
	foreach ($VMItem in $VM)
	{
		$VMObject = $null
		try 
		{
			switch ($VMItem.GetType().FullName)
			{
				'Microsoft.HyperV.PowerShell.VirtualMachine'
				{
					$VMObject = Get-WmiObject -ComputerName $VM.ComputerName -Namespace 'rootvirtualizationv2' -Class 'Msvm_ComputerSystem' -Filter ('Name="{0}"' -f $VMItem.Id) -ErrorAction Stop
				}
				'System.GUID'
				{
					$VMObject = Get-WmiObject -ComputerName $ComputerName -Namespace 'rootvirtualizationv2' -Class 'Msvm_ComputerSystem' -Filter ('Name="{0}"' -f $VMItem) -ErrorAction Stop
				}
				'System.Management.ManagementObject'
				{
					switch ($VMItem.ClassPath.ClassName)
					{
						'Msvm_ComputerSystem'
						{
							$VMObject = $VMItem
						}
						'MSCluster_Resource'
						{
							$VMObject = Get-WmiObject -ComputerName $VMItem.ClassPath.Server -Namespace 'rootvirtualizationv2' -Class 'Msvm_ComputerSystem' -Filter ('Name="{0}"' -f $VMItem.PrivateProprties.VmID) -ErrorAction Stop
						}
						default
						{
							$ArgEx = New-Object System.ArgumentException(('Cannot accept objects of type {0}' -f $VM.ClassPath.ClassName), 'VM')
							throw($ArgEx)
						}
					}
				}

				'System.String'
				{
					if ($VMItem -ne $ComputerName -and $VMItem -ne $env:COMPUTERNAME)
					{
						$VMObject = Get-WmiObject -ComputerName $ComputerName -Namespace 'rootvirtualizationv2' -Class 'Msvm_ComputerSystem' -Filter ('ElementName="{0}"' -f $VMItem) -ErrorAction Stop | select -First 1
					}
				}

				default
				{
					$ArgEx = New-Object System.ArgumentException(('Unable to process objects of type {0}' -f $VMItem.GetType().FullName), 'VM')
					throw($ArgEx)
				}
			}
			if (-not $VMObject)
			{
				throw('Unable to process input object {0}' -f $VMItem.ToString())
			}
		}
		catch
		{
			Write-Error -Exception $_.Exception -ErrorAction Continue
			continue
		}

		$VMObjectComputerName = $VMObject.__SERVER
		$RelatedVMSettings = $VMObject.GetRelated('Msvm_VirtualSystemSettingData') | select -Unique
		$VMSettings = $RelatedVMSettings | where -Property VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized'
		$VMHardDisks = $null
		$VMHardDisks = $RelatedVMSettings.GetRelated() | where -Property ResourceSubType -eq 'Microsoft:Hyper-V:Virtual Hard Disk' -ErrorAction SilentlyContinue
		$VMRemovableDisks = $null
		$VMRemovableDisks = $RelatedVMSettings.GetRelated() | where -Property ResourceSubType -match 'Microsoft:Hyper-V:Virtual (CD/DVD|Floppy) Disk' -ErrorAction SilentlyContinue

		$RootLocation = Parse-Location -Path $VMSettings.ConfigurationDataRoot -DisksOnly $DisksOnly

		$Report = New-StorageConsistencyReport
		$Report.Name = $VMObject.ElementName
		$Report.VMId = $VMObject.Name
		$Report.ComputerName = $VMObjectComputerName
		$Report.Consistent = $true

		Process-Location -Report ([ref]$Report) -Component 'Configuration' -Location $VMSettings.ConfigurationDataRoot -DisksOnly $DisksOnly -RootLocation $RootLocation
		Process-Location -Report ([ref]$Report) -Component 'Checkpoints' -Location $VMSettings.SnapshotDataRoot -DisksOnly $DisksOnly -RootLocation $RootLocation
		Process-Location -Report ([ref]$Report) -Component 'SecondLevelPaging' -Location $VMSettings.SwapFileDataRoot -DisksOnly $DisksOnly -RootLocation $RootLocation

		foreach ($VMHardDisk in $VMHardDisks)
		{
			Process-Location -Report ([ref]$Report) -Component 'Virtual Hard Disk' -Location $VMHardDisks.HostResource[0] -DisksOnly $DisksOnly -RootLocation $RootLocation -TrimFile $true -IgnoreVHDFolder $IgnoreVHDFolder.ToBool()
		}

		foreach ($VMRemovableDisk in $VMRemovableDisks)
		{
			Process-Location -Report ([ref]$Report) -Component 'CD/DVD Image' -Location $VMRemovableDisk.HostResource[0] -DisksOnly $DisksOnly -RootLocation $RootLocation -TrimFile $true -IgnoreVHDFolder $IgnoreVHDFolder.ToBool()
		}

		$Report
	}
}
#} # Uncomment this line to use as a dot-sourced function or in a profile. Also "function" and opening brace lines near top of script

 

The post Free Hyper-V Script: Virtual Machine Storage Consistency Diagnosis appeared first on Altaro DOJO | Hyper-V.

]]>
https://www.altaro.com/hyper-v/free-script-vm-storage-diagnosis/feed/ 1
7 Powerful Scripts for Practical Hyper-V Network Configurations https://www.altaro.com/hyper-v/practical-hyper-v-network-configurations/ https://www.altaro.com/hyper-v/practical-hyper-v-network-configurations/#comments Thu, 23 Nov 2017 15:05:46 +0000 https://www.altaro.com/hyper-v/?p=15359 Get a quick start in configuring networking on a new Hyper-V host. Several scripts are included with appropriate presets for different uses.

The post 7 Powerful Scripts for Practical Hyper-V Network Configurations appeared first on Altaro DOJO | Hyper-V.

]]>

I firmly believe in empowerment. I feel that I should supply you with knowledge, provide you with how-tos, share insights and experiences, and release you into the world to make your own decisions. However, I came to that approach by standing at the front of a classroom. During class, we’d almost invariably walk through exercises. Since this is a blog and not a classroom, I do things differently. We don’t have common hardware in a controlled environment, so I typically forgo the exercises bit. As a result, that leaves a lot of my readers at the edge of a cliff with no bridge to carry them from theory to practice. And, of course, there are those of you that would love to spend time reading about concepts but just really need to get something done right now. If you’re stopped at Hyper-V networking, this is the article for you.

Script Inventory

These scripts are included in this article:

Basic Usage

I’m going to show each item as a stand-alone script. First, you’ll locate the one that best aligns with what you’re trying to accomplish. You’ll copy/paste that into a .ps1 PowerShell script file on your system. You’ll need to edit the script to provide information about your environment so that it will work for you. I’ll have you set each of those items at the beginning of the script. Then, you’ll just need to execute the script on your host.

Most scripts will have its own “basic usage” heading that explains a bit about how you’d use it without modification.

Enhanced Usage

I could easily compile these into standalone executables that you couldn’t tinker with. Even though I want to give you a fully prepared springboard, I also want you to learn how the system works and what you’re doing to it.

Most scripts will have its own “enhanced usage” heading that gives some ideas how you might exploit or extend it yourself.

Configure networking for a single host with a single adapter

Use this script for a standalone system that only has one physical adapter. It will:

Basic Usage for this Script

You just need to enter the necessary information for these items and execute it.

Advanced Usage for this Script

As-is, this script should be complete for most typical single-adapter systems. You might choose to disable some items. For instance, if you are using this on Windows 10, you might not want to provide a fixed IP address. In that case, just put a # sign at the beginning of lines 42 onward. When the virtual network adapter is created, it will remain in DHCP mode.

[CmdletBinding()]param()
#requires -Modules Hyper-V, NetAdapter

## User Modifications Start Here ##
$VirtualSwitchName = 'vSwitch'

# use Get-NetAdapter to locate name for the following item
$Net1AdapterName = 'Ethernet'

$ManagementIP = '192.168.25.10'
$ManagementSubnet = '255.255.255.0'
$ManagementGateway = '192.168.25.1'
$ManagementVLAN = 0 # leave at 0 if no VLAN

$DNSServer1 = '192.168.25.5'
$DNSServer2 = '' # leave blank if no secondary DNS

## User Modifications End Here ##

$Adapter1 = Get-NetAdapter -Name $Net1AdapterName

Write-Verbose -Message 'Disabling VMQ'
$VMQProperties = Get-NetAdapterAdvancedProperty -Name $Adapter1.Name | where DisplayName -Match 'V.*M.*Q'
$VMQProperties | foreach {
    if ($_.RegistryValue -eq 1) { Set-NetAdapterAdvancedProperty -InputObject $_ -RegistryValue 0 }
}

Write-Verbose -Message 'Creating virtual switch'
$VMSwitch = New-VMSwitch -Name $VirtualSwitchName -NetAdapterName $TeamName -AllowManagementOS $false -MinimumBandwidthMode Weight

Write-Verbose -Message 'Creating virtual management adapter'
Add-VMNetworkAdapter -ManagementOS -Name 'Management' -SwitchName $VirtualSwitchName
if ($ManagementVLAN -gt 0)
{
	Write-Verbose -Message 'Setting management adapter VLAN'
	Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName 'Management' -Access -VlanId $ManagementVLAN
}

Write-Verbose -Message 'Setting management adapter IP information'
#netsh always works; New-NetIPAddress will fail if it thinks the adapter is disconnected
netsh interface ip set address 'vEthernet (Management)' static $ManagementIP $ManagementSubnet $ManagementGateway 1
$DNSServers = New-Object System.Collections.ArrayList
$DNSServers += $DNSServer1
if ($DNSServer2) { $DNSServers += $DNSServer2 }
Set-DnsClientServerAddress -InterfaceAlias 'vEthernet (Management)' -ServerAddresses $DNSServers

 

Configure a standalone host with 2-4 gigabit adapters for converged networking

Use this script for a standalone host that has between two and four gigabit adapters that you want to use in a converged networking configuration. It will:

  • Create a team on the adapters
  • Disable VMQ for the physical adapters and the teamed adapter
  • Create a virtual switch on the team
  • Create a virtual network adapter for the management operating system to use
  • Optionally place the management adapter into a VLAN
  • Assign an IP, subnet, and gateway to the management adapter
  • Specify one or two DNS servers

Basic Usage for this Script

You just need to enter the necessary information for these items and execute it. Be aware that it will have problems if you already have a team.

Advanced Usage for this Script

This script serves as the base for the remaining scripts on this page. Likewise, you could use it as a base for your own. You could also use any of the items as examples for whatever similar actions you wish to accomplish in your own scripts.

[CmdletBinding()]param()
#requires -Modules Hyper-V, NetAdapter, NetLbfo

## User Modifications Start Here ##
$VirtualSwitchName = 'vSwitch'
$TeamName = 'vSwitch'

# use Get-NetAdapter to locate names for the following items
$Net1AdapterName = 'Ethernet'
$Net2AdapterName = 'Ethernet 2'
$Net3AdapterName = '' # leave blank if no third adapter
$Net4AdapterName = '' # leave blank if no fourth adapter

$ManagementIP = '192.168.25.10'
$ManagementSubnet = '255.255.255.0'
$ManagementGateway = '192.168.25.1'
$ManagementVLAN = 0 # leave at 0 if no VLAN

$DNSServer1 = '192.168.25.5'
$DNSServer2 = '' # leave blank if no secondary DNS

## User Modifications End Here ##

$Adapter1 = Get-NetAdapter -Name $Net1AdapterName
$Adapter2 = Get-NetAdapter -Name $Net2AdapterName
$Adapter3 = Get-NetAdapter -Name $Net3AdapterName
$Adapter4 = Get-NetAdapter -Name $Net4AdapterName

Write-Verbose -Message 'Creating team'
$Team = New-NetLbfoTeam -Name $VirtualSwitchName -TeamMembers $Adapter1.Name, $Adapter2.Name -TeamingMode SwitchIndependent -LoadBalancingAlgorithm Dynamic -Confirm:$false
$Adapter3, $Adapter4 | foreach {
	if ($_ -ne $null) { $AddedAdapter = Add-NetLbfoTeamMember -Team $TeamName -Name $_.Name -Confirm:$false }
}

$TeamAdapter = Get-NetAdapter 'vSwitch'

Write-Verbose -Message 'Disabling VMQ'
$TeamAdapter, $Adapter1, $Adapter2, $Adapter3, $Adapter4 | foreach {
	if ($_)
 {
		$VMQProperties = Get-NetAdapterAdvancedProperty -Name $_.Name | where DisplayName -Match 'V.*M.*Q'
		if ($VMQProperties)
		{
			$VMQProperties | foreach {
				if ($_.RegistryValue -eq 1) { Set-NetAdapterAdvancedProperty -InputObject $_ -RegistryValue 0 }
			}
		}
	}
}

Write-Verbose -Message 'Creating virtual switch'
$VMSwitch = New-VMSwitch -Name $VirtualSwitchName -NetAdapterName $TeamName -AllowManagementOS $false -MinimumBandwidthMode Weight

Write-Verbose -Message 'Creating virtual management adapter'
Add-VMNetworkAdapter -ManagementOS -Name 'Management' -SwitchName $VirtualSwitchName
if ($ManagementVLAN -gt 0)
{
	Write-Verbose -Message 'Setting management adapter VLAN'
	Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName 'Management' -Access -VlanId $ManagementVLAN
}

Write-Verbose -Message 'Setting management adapter IP information'
netsh interface ip set address 'vEthernet (Management)' static $ManagementIP $ManagementSubnet $ManagementGateway 1 #netsh always works; New-NetIPAddress will fail if it thinks the adapter is disconnected
$DNSServers = New-Object System.Collections.ArrayList
$DNSServers += $DNSServer1
if ($DNSServer2) { $DNSServers += $DNSServer2 }
Set-DnsClientServerAddress -InterfaceAlias 'vEthernet (Management)' -ServerAddresses $DNSServers

 

Configure a standalone host with 2-4 10 GbE adapters for converged networking

Use this script for a standalone host that has between two and four 10GbE adapters that you want to use in a converged networking configuration. It will:

  • Create a team on the adapters
  • Create a virtual switch on the team
  • Create a virtual network adapter for the management operating system to use
  • Optionally place the management adapter into a VLAN
  • Assign an IP, subnet, and gateway to the management adapter
  • Specify one or two DNS servers

It won’t take a great deal of sleuthing to discover that this script is identical to the preceding one, except that it does not disable VMQ.

[CmdletBinding()]param()
#requires -Modules Hyper-V, NetAdapter, NetLbfo

## User Modifications Start Here ##
$VirtualSwitchName = 'vSwitch'
$TeamName = 'vSwitch'

# use Get-NetAdapter to locate names for the following items
$Net1AdapterName = 'Ethernet'
$Net2AdapterName = 'Ethernet 2'
$Net3AdapterName = '' # leave blank if no third adapter
$Net4AdapterName = '' # leave blank if no fourth adapter

$ManagementIP = '192.168.25.164'
$ManagementSubnet = '255.255.255.0'
$ManagementGateway = '192.168.25.1'
$ManagementVLAN = 0 # leave at 0 if no VLAN

$DNSServer1 = '192.168.25.5'
$DNSServer2 = '' # leave blank if no secondary DNS

## User Modifications End Here ##

$Adapter1 = Get-NetAdapter -Name $Net1AdapterName
$Adapter2 = Get-NetAdapter -Name $Net2AdapterName
$Adapter3 = Get-NetAdapter -Name $Net3AdapterName
$Adapter4 = Get-NetAdapter -Name $Net4AdapterName

Write-Verbose -Message 'Creating team'
$Team = New-NetLbfoTeam -Name $VirtualSwitchName -TeamMembers $Adapter1.Name, $Adapter2.Name -TeamingMode SwitchIndependent -LoadBalancingAlgorithm Dynamic -Confirm:$false
$Adapter3, $Adapter4 | foreach {
	if ($_ -ne $null) { $AddedAdapter = Add-NetLbfoTeamMember -Team $TeamName -Name $_.Name -Confirm:$false }
}

$TeamAdapter = Get-NetAdapter 'vSwitch'

Write-Verbose -Message 'Creating virtual switch'
$VMSwitch = New-VMSwitch -Name $VirtualSwitchName -NetAdapterName $TeamName -AllowManagementOS $false -MinimumBandwidthMode Weight

Write-Verbose -Message 'Creating virtual management adapter'
Add-VMNetworkAdapter -ManagementOS -Name 'Management' -SwitchName $VirtualSwitchName
if ($ManagementVLAN -gt 0)
{
	Write-Verbose -Message 'Setting management adapter VLAN'
	Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName 'Management' -Access -VlanId $ManagementVLAN
}

Write-Verbose -Message 'Setting management adapter IP information'
netsh interface ip set address 'vEthernet (Management)' static $ManagementIP $ManagementSubnet $ManagementGateway 1 #netsh always works; New-NetIPAddress will fail if it thinks the adapter is disconnected
$DNSServers = New-Object System.Collections.ArrayList
$DNSServers += $DNSServer1
if ($DNSServer2) { $DNSServers += $DNSServer2 }
Set-DnsClientServerAddress -InterfaceAlias 'vEthernet (Management)' -ServerAddresses $DNSServers

 

Configure a clustered host with 2-4 gigabit adapters for converged networking

Use this script for a host that has between two and four gigabit adapters that will be a member of a cluster. Like the previous scripts, it will employ a converged networking configuration. The script will:

  • Create a team on the adapters
  • Disable VMQ for the physical adapters and the teamed adapter
  • Create a virtual switch on the team
  • Create virtual network adapters for the management operating system to use for management traffic, cluster communications, and Live Migration
  • Optionally place the virtual adapters into VLANs
  • Assign an IP, subnet, and gateway to the management adapter
  • Assign an IP and subnet mask to the cluster and Live Migration adapters
  • Prevent the cluster and Live Migration adapters from registering in DNS
  • Specify one or two DNS servers

Basic Usage for this Script

You just need to enter the necessary information for these items and execute it. It is essentially the same as the stand-alone multi-gigabit adapter script except that it also adds adapters for cluster communications and Live Migration traffic.

It does not arrange the adapters in an optimal order for Live Migration. The cluster will automatically prioritize the cluster and Live Migration adapters over the management adapter, but it might prioritize the cluster adapter over the Live Migration adapter. Practically, that will have no meaningful effect; these designations are mostly cosmetic. If you’d like to force the issue, you’ll need to do so separately. You could, of course, use Failover Cluster Manager for this. I’ve included a script later in this article that makes the setting change for you. You cannot combine these scripts because the cluster must exist before you can specify the Live Migration adapter order. Also, you only need to specify the order one time, not once per node.

Advanced Usage for this Script

You could do a great number of things with this script. One suggestion would be to add cluster creation/join logic. It would be non-trivial, but you’d be able to combine the Live Migration adapter ordering script.

[CmdletBinding()]param()
#requires -Modules Hyper-V, NetAdapter, NetLbfo

## User Modifications Start Here ##
$VirtualSwitchName = 'vSwitch'
$TeamName = 'vSwitch'

# use Get-NetAdapter to locate names for the following items
$Net1AdapterName = 'Ethernet 2'
$Net2AdapterName = 'Ethernet 3'
$Net3AdapterName = 'Ethernet 4' # leave blank if no third adapter
$Net4AdapterName = 'Ethernet 5' # leave blank if no fourth adapter

$ManagementIP = '192.168.25.164'
$ManagementSubnet = '255.255.255.0'
$ManagementGateway = '192.168.25.1'
$ManagementVLAN = 0 # leave at 0 if no VLAN

$ClusterIP = '192.168.10.164'
$ClusterSubnet = '255.255.255.0'
$ClusterVLAN = 0 # leave at 0 if no VLAN

$LiveMigrationIP = '192.168.15.164'
$LiveMigrationSubnet = '255.255.255.0'
$LiveMigrationVLAN = 0 # leave at 0 if no VLAN

$DNSServer1 = '192.168.25.5'
$DNSServer2 = '192.168.25.6' # leave blank if no secondary DNS

## User Modifications End Here ##

$Adapter1 = Get-NetAdapter -Name $Net1AdapterName
$Adapter2 = Get-NetAdapter -Name $Net2AdapterName
$Adapter3 = Get-NetAdapter -Name $Net3AdapterName
$Adapter4 = Get-NetAdapter -Name $Net4AdapterName

Write-Verbose -Message 'Creating team'
$Team = New-NetLbfoTeam -Name $VirtualSwitchName -TeamMembers $Adapter1.Name, $Adapter2.Name -TeamingMode SwitchIndependent -LoadBalancingAlgorithm Dynamic -Confirm:$false
$Adapter3, $Adapter4 | foreach {
	if ($_ -ne $null) { $AddedAdapter = Add-NetLbfoTeamMember -Team $TeamName -Name $_.Name -Confirm:$false }
}

$TeamAdapter = Get-NetAdapter 'vSwitch'

Write-Verbose -Message 'Disabling VMQ'
$TeamAdapter, $Adapter1, $Adapter2, $Adapter3, $Adapter4 | foreach {
	if ($_)
 {
		$VMQProperties = Get-NetAdapterAdvancedProperty -Name $_.Name | where DisplayName -Match 'V.*M.*Q'
		if ($VMQProperties)
		{
			$VMQProperties | foreach {
				if ($_.RegistryValue -eq 1) { Set-NetAdapterAdvancedProperty -InputObject $_ -RegistryValue 0 }
			}
		}
	}
}

Write-Verbose -Message 'Creating virtual switch'
$VMSwitch = New-VMSwitch -Name $VirtualSwitchName -NetAdapterName $TeamName -AllowManagementOS $false -MinimumBandwidthMode Weight

Write-Verbose -Message 'Creating virtual management adapter'
Add-VMNetworkAdapter -ManagementOS -Name 'Management' -SwitchName $VirtualSwitchName
if ($ManagementVLAN -gt 0)
{
	Write-Verbose -Message 'Setting management adapter VLAN'
	Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName 'Management' -Access -VlanId $ManagementVLAN
}

Write-Verbose -Message 'Setting management adapter IP information'
netsh interface ip set address 'vEthernet (Management)' static $ManagementIP $ManagementSubnet $ManagementGateway 1 #netsh always works; New-NetIPAddress will fail if it thinks the adapter is disconnected

Write-Verbose -Message 'Creating virtual cluster traffic adapter'
Add-VMNetworkAdapter -ManagementOS -Name 'Cluster' -SwitchName $VirtualSwitchName
if ($ClusterVLAN -gt 0)
{
	Write-Verbose -Message 'Setting cluster adapter VLAN'
	Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName 'Cluster' -Access -VlanId $ClusterVLAN
}
Set-DnsClient -InterfaceAlias 'vEthernet (Cluster)' -RegisterThisConnectionsAddress $false
netsh interface ip set address 'vEthernet (Cluster)' static $ClusterIP $ClusterSubnet

Write-Verbose -Message 'Creating virtual Live Migration adapter'
Add-VMNetworkAdapter -ManagementOS -Name 'Live Migration' -SwitchName $VirtualSwitchName
if ($LiveMigrationVLAN -gt 0)
{
	Write-Verbose -Message 'Setting Live Migration adapter VLAN'
	Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName 'Live Migration' -Access -VlanId $LiveMigrationVLAN
}
Set-DnsClient -InterfaceAlias 'vEthernet (Live Migration)' -RegisterThisConnectionsAddress $false
netsh interface ip set address 'vEthernet (Live Migration)' static $LiveMigrationIP $LiveMigrationSubnet

Write-Verbose -Message 'Setting DNS client addresses'
$DNSServers = New-Object System.Collections.ArrayList
$DNSServers += $DNSServer1
if ($DNSServer2) { $DNSServers += $DNSServer2 }
Set-DnsClientServerAddress -InterfaceAlias 'vEthernet (Management)' -ServerAddresses $DNSServers

 

Configure a clustered host with 2-4 10 GbE adapters for converged networking

This script is identical to the preceding except that it leaves VMQ enabled. It does the following:

  • Create a team on the adapters
  • Create a virtual switch on the team
  • Create virtual network adapters for the management operating system to use for management traffic, cluster communications, and Live Migration
  • Optionally place the virtual adapters into VLANs
  • Assign an IP, subnet, and gateway to the management adapter
  • Assign an IP and subnet mask to the cluster and Live Migration adapters
  • Prevent the cluster and Live Migration adapters from registering in DNS
  • Specify one or two DNS servers

Basic Usage for this Script

These notes are identical to those of the preceding script.

You just need to enter the necessary information for these items and execute it. It is essentially the same as the stand-alone multi-gigabit adapter script except that it also adds adapters for cluster communications and Live Migration traffic.

It does not arrange the adapters in an optimal order for Live Migration. The cluster will automatically prioritize the cluster and Live Migration adapters over the management adapter, but it might prioritize the cluster adapter over the Live Migration adapter. Practically, that will have no meaningful effect; these designations are mostly cosmetic. If you’d like to force the issue, you’ll need to do so separately. You could, of course, use Failover Cluster Manager for this. I’ve included a script later in this article that makes the setting change for you. You cannot combine these scripts because the cluster must exist before you can specify the Live Migration adapter order. Also, you only need to specify the order one time, not once per node.

Advanced Usage for this Script

These notes are identical to those of the preceding script.

You could do a great number of things with this script. One suggestion would be to add cluster creation/join logic. It would be non-trivial, but you’d be able to combine the Live Migration adapter ordering script.

[CmdletBinding()]param()
#requires -Modules Hyper-V, NetAdapter, NetLbfo

## User Modifications Start Here ##
$VirtualSwitchName = 'vSwitch'
$TeamName = 'vSwitch'

# use Get-NetAdapter to locate names for the following items
$Net1AdapterName = 'Ethernet 2'
$Net2AdapterName = 'Ethernet 3'
$Net3AdapterName = 'Ethernet 4' # leave blank if no third adapter
$Net4AdapterName = 'Ethernet 5' # leave blank if no fourth adapter

$ManagementIP = '192.168.25.164'
$ManagementSubnet = '255.255.255.0'
$ManagementGateway = '192.168.25.1'
$ManagementVLAN = 0 # leave at 0 if no VLAN

$ClusterIP = '192.168.10.164'
$ClusterSubnet = '255.255.255.0'
$ClusterVLAN = 0 # leave at 0 if no VLAN

$LiveMigrationIP = '192.168.15.164'
$LiveMigrationSubnet = '255.255.255.0'
$LiveMigrationVLAN = 0 # leave at 0 if no VLAN

$DNSServer1 = '192.168.25.5'
$DNSServer2 = '192.168.25.6' # leave blank if no secondary DNS

## User Modifications End Here ##

$Adapter1 = Get-NetAdapter -Name $Net1AdapterName
$Adapter2 = Get-NetAdapter -Name $Net2AdapterName
$Adapter3 = Get-NetAdapter -Name $Net3AdapterName
$Adapter4 = Get-NetAdapter -Name $Net4AdapterName

Write-Verbose -Message 'Creating team'
$Team = New-NetLbfoTeam -Name $VirtualSwitchName -TeamMembers $Adapter1.Name, $Adapter2.Name -TeamingMode SwitchIndependent -LoadBalancingAlgorithm Dynamic -Confirm:$false
$Adapter3, $Adapter4 | foreach {
	if ($_ -ne $null) { $AddedAdapter = Add-NetLbfoTeamMember -Team $TeamName -Name $_.Name -Confirm:$false }
}

$TeamAdapter = Get-NetAdapter 'vSwitch'

Write-Verbose -Message 'Creating virtual switch'
$VMSwitch = New-VMSwitch -Name $VirtualSwitchName -NetAdapterName $TeamName -AllowManagementOS $false -MinimumBandwidthMode Weight

Write-Verbose -Message 'Creating virtual management adapter'
Add-VMNetworkAdapter -ManagementOS -Name 'Management' -SwitchName $VirtualSwitchName
if ($ManagementVLAN -gt 0)
{
	Write-Verbose -Message 'Setting management adapter VLAN'
	Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName 'Management' -Access -VlanId $ManagementVLAN
}

Write-Verbose -Message 'Setting management adapter IP information'
netsh interface ip set address 'vEthernet (Management)' static $ManagementIP $ManagementSubnet $ManagementGateway 1 #netsh always works; New-NetIPAddress will fail if it thinks the adapter is disconnected

Write-Verbose -Message 'Creating virtual cluster traffic adapter'
Add-VMNetworkAdapter -ManagementOS -Name 'Cluster' -SwitchName $VirtualSwitchName
if ($ClusterVLAN -gt 0)
{
	Write-Verbose -Message 'Setting cluster adapter VLAN'
	Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName 'Cluster' -Access -VlanId $ClusterVLAN
}
Set-DnsClient -InterfaceAlias 'vEthernet (Cluster)' -RegisterThisConnectionsAddress $false
netsh interface ip set address 'vEthernet (Cluster)' static $ClusterIP $ClusterSubnet

Write-Verbose -Message 'Creating virtual Live Migration adapter'
Add-VMNetworkAdapter -ManagementOS -Name 'Live Migration' -SwitchName $VirtualSwitchName
if ($LiveMigrationVLAN -gt 0)
{
	Write-Verbose -Message 'Setting Live Migration adapter VLAN'
	Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName 'Live Migration' -Access -VlanId $LiveMigrationVLAN
}
Set-DnsClient -InterfaceAlias 'vEthernet (Live Migration)' -RegisterThisConnectionsAddress $false
netsh interface ip set address 'vEthernet (Live Migration)' static $LiveMigrationIP $LiveMigrationSubnet

Write-Verbose -Message 'Setting DNS client addresses'
$DNSServers = New-Object System.Collections.ArrayList
$DNSServers += $DNSServer1
if ($DNSServer2) { $DNSServers += $DNSServer2 }
Set-DnsClientServerAddress -InterfaceAlias 'vEthernet (Management)' -ServerAddresses $DNSServers

 

Set preferred order for cluster Live Migration networks

This script aligns with the two preceding scripts to ensure that the cluster chooses the named “Live Migration” adapter first when moving virtual machines between nodes. The “Cluster” virtual adapter will be used second. The management adapter will be used as the final fallback.

Basic Usage for this Script

Use this script after you’ve run one of the above two clustered host scripts and joined them into a cluster.

Advanced Usage for this Script

Modify this to change the order of Live Migration adapters. You must specify all adapters recognized by the cluster. Check the “MigrationExcludeNetworks” registry key that’s in the same location as “MigrationNetworkOrder”.

[CmdletBinding()]param()
#requires -Modules FailoverClusters

$NetworksToInclude = [String]::Join(';', @(
    (Get-ClusterNetwork -Name 'Live Migration').Id,
    (Get-ClusterNetwork -Name 'Cluster').Id,
    (Get-ClusterNetwork -Name 'Management').Id
))

$VMParametersKey = 'HKLM:ClusterResourceTypesVirtual MachineParameters'

Write-Verbose -Message 'Setting Live Migration adapter order'
Set-ItemProperty -Path $VMParametersKey -Name 'MigrationNetworkOrder' -Value $NetworksToInclude

 

Exclude cluster networks from Live Migration

This script is intended to be used as an optional adjunct to the preceding script. Since my scripts set up all virtual adapters to be used in Live Migration, the network names used here are fabricated.

Basic Usage for this Script

You’ll need to set the network names to match yours, but otherwise, the script does not need to be altered.

Advanced Usage for this Script

This script will need to be modified in order to be used at all.

[CmdletBinding()]param()
#requires -Modules FailoverClusters

$NetworksToExclude = [String]::Join(';', @(
    (Get-ClusterNetwork -Name 'Storage1').Id,
    (Get-ClusterNetwork -Name 'Storage2').Id
))

$VMParametersKey = 'HKLM:ClusterResourceTypesVirtual MachineParameters'

Write-Verbose -Message 'Excluding adapters from Live Migration'
Set-ItemProperty -Path $VMParametersKey -Name 'MigrationExcludeNetworks' -Value $NetworksToExclude

 

The post 7 Powerful Scripts for Practical Hyper-V Network Configurations appeared first on Altaro DOJO | Hyper-V.

]]>
https://www.altaro.com/hyper-v/practical-hyper-v-network-configurations/feed/ 4