0

I recently started teaching myself about PowerShell scripting. I'm using Windows 10 Professional at home.

This is what I have so far for my script:

Start-Job 
{
# Create new VMs based on the names given in the "VMName.csv" file.

$NewVM = Import-Csv "D:\Hyper-V\Hyper_V_Scripts\Test_Gen2_Virtual_Machine_Scratch.csv"

# Specifies VMName and Generation Number without attaching VHDX files.

$NewVM | ForEach-Object {New-VM -Name $_.VMName -Generation $_.Generation -NoVHD}

# Sets Startup, Mininum, and Maximum Memory fields.

$NewVM | ForEach-Object {Set-VMMemory -VMName $_.VMName -StartupBytes 1GB -MinimumBytes 512MB -MaximumBytes $_.MaxMemory}

# Creates VHDX file based on the VM name.

$NewVM | ForEach-Object {New-VHD -Path ("F:\Hyper-v_Storage\"+$_.VMName+".vhdx") -SizeBytes $_.VHDXSize}

# Adds VHDX to VMs.

$NewVM | ForEach-Object {Add-VMHardDiskDrive -VMName $_.VMName -Path ("F:\Hyper-V_Storage\"+$_.VMName+".vhdx") -ControllerNumber 0 -ControllerLocation 0}

# Add VMDVDDrive with ISO to VMs.

$NewVM | ForEach-Object {Add-VMDvdDrive -VMName $_.VMName -Path ("D:\iso\"+$_.ISO+".iso") -ControllerNumber 0 -ControllerLocation 1}

# Change firmware's boot order to CD/DVD, Hard Disk Drive, and Network Adapter

$NewVM | ForEach-Object {Set-VMFirmware -VMName $_.VMName -BootOrder (Get-VM -Name $_.VMName | Get-VMDvdDrive),(Get-VM -Name $_.VMName | Get-VMHardDiskDrive),(Get-VM -Name $_.VMName | Get-VMNetworkAdapter)}

# Add RemoteFX3DAdapter to certain VMs

$NewVM | Where-Object {$_.VMName -like "Chief_Architect - Windows 10"} | ForEach-Object {Add-VMRemoteFx3dVideoAdapter -VMName $_.VMName}

# Creates External Network Switch

# New-VMSwitch "External Realtek NIC" -NetAdapterName "Realtek_NIC" -AllowManagementOS $False

# Adds External Realtek_NIC to all VMs


$NewVM | ForEach-Object {Add-VMNetworkAdapter -VMName $_.VMName -SwitchName "External Realtek NIC" -Name "External Realtek"} 
}

Is it wise to do the component under # Creates External Switch, considering that I only need to do that once, or should I just run that separately since it'll be a one time run?

Also, every time I run the last line, under the # Adds External Realtek_NIC to all VMs, I get the following error:

Add-VMNetworkAdapter : Cannot validate argument on parameter 'VMName'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At line:1 char:55
+ $NewVM | ForEach-Object {Add-VMNetworkAdapter -VMName $_.VMName}
+                                                       ~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Add-VMNetworkAdapter], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.HyperV.PowerShell.Commands.AddVMNetworkAdapter

Why do I get that error, when the CSV file I created works just fine in all other places?

Contents of the CSV File:

VMName,MaxMemory,ISO,VHDXSize,Generation
ADDS-DHCP-DNS-WDS - Windows Server 2016 TP 5,2147483648,TP_5\Windows_Server_2016\14300.1000.160324-1723.RS1_RELEASE_SVC_SERVER_OEMRET_X64FRE_EN-US,107374182400,2
Chief_Architect - Windows 10,6442450944,Win10_1607_English_x64,37580963840,2
Development - Windows 10,4294967296,Win10_1607_English_x64,161061273600,2
PFSense,1073741824,pfSense-CE-2.3.1-RELEASE-amd64,2147483648,2
Steam - Windows 10,6442450944,Win10_1607_English_x64,322122547200,2
Storage Pool - Windows Server 2016 TP 5,2147483648,TP_5\Windows_Server_2016\14300.1000.160324-1723.RS1_RELEASE_SVC_SERVER_OEMRET_X64FRE_EN-US,42949672960,2
Winixhub - Windows 10,4294967296,Win10_1607_English_x64,53687091200,2
Winixhub Web Server - Windows Server 2016 TP 5,4294967296,TP_5\Windows_Server_2016\14300.1000.160324-1723.RS1_RELEASE_SVC_SERVER_OEMRET_X64FRE_EN-US,85899345920,2
mkrieger1
  • 19,194
  • 5
  • 54
  • 65
  • Q on style: Why do you run `$NewVM | ForEach-Object { ... }` multiple times, once for each command you do? Wouldn't it be easier, faster, and more importantly far better style - to only loop once, and on each iteration, run all of the commands on that iteration's VM? – underscore_d Aug 17 '16 at 20:03
  • ...Your CSV as pasted has blank lines in between every real one. CSVs are assumed to have 1 line of headers (optional) and then 1 line for each record in the file. Is that really what you're feeding into your command? If so, all imported variables will be null/empty on every other line, and it's a wonder you didn't get an error much earlier... which makes me think it was just an accident during pasting, surely. The latter explanation might be evidenced by how your PS script has the same issue. – underscore_d Aug 17 '16 at 20:06
  • Yes, it was a mistake during the pasting. How would you recommend I improve the look/style of it to fit what your second post suggests? – KennethWC Aug 17 '16 at 20:10
  • OK. Please accept my edit to add the missing codeboxes (and title!) and, while doing so, select the option to "improve" and remove the added lines. As for the loop, well, it's just basic looping: just do the `ForEach` once and put all the commands within its braces. The braces exist precisely so that you can have multiple lines and hence commands within them. See, for example: http://www.powershelladmin.com/wiki/PowerShell_foreach_loops_and_ForEach-Object#Spanning_Multiple_Lines This is also related, the opposite way: http://stackoverflow.com/questions/3235850/how-to-enter-a-multi-line-command – underscore_d Aug 17 '16 at 20:11
  • My apologies, I didn't realize that's what was meant by reformatting the post. Now I know better after "TheMadTechnician" corrected it. I'll keep that in mind for the future. – KennethWC Aug 17 '16 at 20:46

1 Answers1

1

Ok, I kept your comments, but combined all your various loops through $NewVM into one loop (and moved the switch creation before that loop, still commented out). I also changed the GFX command to a If statement, so it will only apply that command to the specified VM. Lastly, I fixed your Add-VMNetworkAdapter command. That one was not working because you had an invalid parameter set. If you run Get-Help Add-VMNetworkAdapter you'll see the parameter sets are:

Add-VMNetworkAdapter [-VMName] <string[]> [-CimSession <CimSession[]>] [-ComputerName <string[]>] [-Credential <pscredential[]>] [-SwitchName <string>] [-IsLegacy <bool>] 
[-Name <string>] [-DynamicMacAddress] [-StaticMacAddress <string>] [-Passthru] [-ResourcePoolName <string>] [-DeviceNaming {On | Off}] [-WhatIf] [-Confirm]  [<CommonParameters>]

Add-VMNetworkAdapter [-CimSession <CimSession[]>] [-ComputerName <string[]>] [-Credential <pscredential[]>] [-ManagementOS] [-SwitchName <string>] [-Name <string>] 
[-DynamicMacAddress] [-StaticMacAddress <string>] [-Passthru] [-DeviceNaming {On | Off}] [-WhatIf] [-Confirm]  [<CommonParameters>]

Add-VMNetworkAdapter [-VM] <VirtualMachine[]> [-SwitchName <string>] [-IsLegacy <bool>] [-Name <string>] [-DynamicMacAddress] [-StaticMacAddress <string>] [-Passthru] 
[-ResourcePoolName <string>] [-DeviceNaming {On | Off}] [-WhatIf] [-Confirm]  [<CommonParameters>]

You tried to use -VMName from the first set, and -SwitchName from the last set. I just changed it to capture the VM when it's created in a variable, and then use that for the -VM parameter. I also moved that command to just after making the VM, so that the data in the variable is pristine. Here's what I ended up with:

# Create new VMs based on the names given in the "VMName.csv" file.
$NewVM = Import-Csv "D:\Hyper-V\Hyper_V_Scripts\Test_Gen2_Virtual_Machine_Scratch.csv"

# Creates External Network Switch
# New-VMSwitch "External Realtek NIC" -NetAdapterName "Realtek_NIC" -AllowManagementOS $False

$NewVM | ForEach-Object {
    # Specifies VMName and Generation Number without attaching VHDX files.
    $VM = New-VM -Name $_.VMName -Generation $_.Generation -NoVHD

    # Adds External Realtek_NIC to all VMs
    Add-VMNetworkAdapter -VM $VM -SwitchName "External Realtek NIC" -Name "External Realtek"

    # Sets Startup, Mininum, and Maximum Memory fields.
    $VMMem = Set-VMMemory -VMName $_.VMName -StartupBytes 1GB -MinimumBytes 512MB -MaximumBytes $_.MaxMemory

    # Creates VHDX file based on the VM name.
    $VHD = New-VHD -Path ("F:\Hyper-v_Storage\"+$_.VMName+".vhdx") -SizeBytes $_.VHDXSize

    # Adds VHDX to VMs
    $VMHD = Add-VMHardDiskDrive -VMName $_.VMName -Path ("F:\Hyper-V_Storage\"+$_.VMName+".vhdx") -ControllerNumber 0 -ControllerLocation 0

    # Add VMDVDDrive with ISO to VMs.
    $VMDVD = Add-VMDvdDrive -VMName $_.VMName -Path ("D:\iso\"+$_.ISO+".iso") -ControllerNumber 0 -ControllerLocation 1

    # Change firmware's boot order to CD/DVD, Hard Disk Drive, and Network Adapter
    Set-VMFirmware -VMName $_.VMName -BootOrder (Get-VM -Name $_.VMName | Get-VMDvdDrive),(Get-VM -Name $_.VMName | Get-VMHardDiskDrive),(Get-VM -Name $_.VMName | Get-VMNetworkAdapter)

    # Add RemoteFX3DAdapter to certain VMs
    If($_.VMName -like "Chief_Architect - Windows 10") {Add-VMRemoteFx3dVideoAdapter -VMName $_.VMName}
}
TheMadTechnician
  • 34,906
  • 3
  • 42
  • 56
  • I revised my script, and everything works aside from adding the adapter. I ran your rendition of the script, and everything worked except for the boot order. – KennethWC Aug 17 '16 at 22:28
  • Ah, well, you can always use your original command for that line. I'll edit my answer to use your original for that line. Not sure why it would fail, but if yours works that's an easy fix. – TheMadTechnician Aug 17 '16 at 22:37