Create a VM from existing VHD file
A tricky task with creating an Azure ARM Virtual machine specially from an existing VHD. We had immense trouble getting this right for a project, until we managed to find the right script to be used for the purpose.
There are 2 scenarios in which you will create a Virtual Machine from a VHD (Virtual Hard Disk) where
- The VHD is already sysprep’d and uploaded to the storage account – In this scenario, the Set-AzureRmVMOSDisk will have -CreateOption set to fromImage and use the VHD as the source image.
- The VHD is not sysprep’d and need to be deployed as-is to test its functionality – In this case, the Set-AzureRmVMOSDisk will have -CreateOption set to Attach and attach the provided VHD.
This script contains the code for both the scenario and all the users need to ensure is they un-comment the lines based on their requirement.
NOTE: You can create a set of VMs based on one of these need at one go, i.e. 5 VMs based on sysprep’d VHD or attach existing VHD, not a mix of them in the same run.
The script uses a comma separated .csv file to provide the input and produces a .csv file with the virtual machine name and the IP address assigned to them.
You can get the input file downloaded here
Pre Requisite to Provisioning a Virtual Machine
- Internet connection
- Valid Azure Subscription
- The person who will perform the below steps must have one of the following roles in the Azure subscription
- Owner
- Contributor
- Latest version of Microsoft .NET Framework (download link)
- Latest version of Microsoft Windows Management Framework (download link)
- Latest version of Microsoft PowerShell (download link including detailed installation guide)
- Latest version of Azure cmdlets (installation guide), although steps are provided below
- Some familiarity with PowerShell ISE, although not necessary (documentation)
- Some familiarity with the Azure Portal would be beneficial, although not necessary
- Some familiarity with PowerShell scripting would be beneficial, although not necessary
Script 1: A Simple script which can create one VM using a non-sysprep’d VHD – (no input files required)
# Set variable for the script $subscriptionId = 'Subscription' $storageAccountName = '<StorageAccoutName>' # Login into Azure and select the desired subscription Login-AzureRmAccount # Get the storage account $storageAccount = Get-AzureRmStorageAccount | ? StorageAccountName -EQ $storageAccountName # Select your Azure subscriptino # Select-AzureRmSubscription -SubscriptionId $subscriptionId # get availability set and NIC for the machine $nic = Get-AzureRmNetworkInterface -ResourceGroupName <ResourceGroupName> -Name <NicNAme> # create VM config with machine size and availability set ID $vm = New-AzureRMVMConfig -VMName <VMName> -VMSize Standard_D2 # add the existing OS disk - if windows change -Linux to -Windows Set-AzureRmVMOSDisk -VM $vm -Name osDisk -VhdUri "<vhd file URL from Microsoft Storage Explorer or portal> " -Caching ReadWrite -CreateOption Attach -Linux # attach network card which in turn configures all further network settings Add-AzureRmVMNetworkInterface -VM $vm -Id $nic.Id # recreate the VM with the new settings New-AzureRMVM -ResourceGroupName <ResourceGroupName> -Location "<region e.g. westeurope>" -VM $vm –Verbose
Script 2: Script to deploy 1 or more virtual machines based out of custom VHD files.
<# Description: Script to create Azure ARM Resource Group and Deploying VM's Pre-requisite: AzureRM module to be installed Resource Group Storage Subnet Data Drives VM -> nic, datadrive, storage, Resourcegrp Tasks - > 1) Minimize the CSV columns 2) Tags 3) ErrorHandling 4) Outcome shud be recorded in a log or csv #> #Login to Azure using the username or password Login-AzureRmAccount #Set the output file parameters $CurrentPath = Split-Path $MyInvocation.MyCommand.Definition $date = Get-Date -UFormat "%Y-%m-%d-%H-%M" $Outputfile = $CurrentPath + "New_VM_Details_" + $date + ".csv" # Set values for existing resource group and storage account names try { $VMDetails = Import-Csv "$CurrentPath\InputVMDetails.csv" Remove-Item "$CurrentPath/AzureVMLog" -Force -Recurse -ErrorAction SilentlyContinue | Out-Null Start-Sleep -Seconds 1 New-Item -Path $CurrentPath -Name AzureVMLog -ItemType Directory -Force | Out-Null } catch { Write-Verbose -Verbose "Could not Import the CSV file !!!" return } write-verbose -verbose "VM Details will be stored in [ $Outputfile ] file" $FinalVMDetails = @() #Loop Through the VM Details in the CSV file to read and validate the data before creating the VMs ForEach($VM_object in $VMDetails) { $FinalVMDetail = New-Object –TypeName PSObject $FinalVMDetail | Add-Member –MemberType NoteProperty –Name 'VM Name' –Value $VM_object.hostname $FinalVMDetail | Add-Member -MemberType NoteProperty -Name 'IP Address' -value 'Fail' $FinalVMDetail | Add-Member -MemberType NoteProperty -Name 'Status' -value 'Fail' $ErrorLogFile = $VM_object.hostname + "_Error_.log" New-Item -Name $ErrorLogFile -ItemType File -Path "$CurrentPath\AzureVMLog" -Force | Out-Null $ErrorLogFile = "$CurrentPath\AzureVMLog\$ErrorLogFile" # Selecting the Azure Subscription for creation of the VM write-verbose -verbose "Selecting the Azure Subscription for VM Creation..." try { $subscrbtion=Select-AzureRmSubscription -SubscriptionId $VM_object.subscriptionName -ErrorAction Stop write-verbose -verbose "Selected the Azure Subscription for VM Creation.." } catch { Write-Verbose -Verbose "Could not find Subscription name " $VM_object.subscriptionName [string](Get-Date) + $VM_object.subscriptionName | Out-File -FilePath $ErrorLogFile -Append } $vmName=$VM_object.hostName #Creation of VM Starts here Write-Verbose -Verbose "Provisioning of VM $vmName ......... " if((Get-AzureRmVm -ErrorAction Stop | Where-Object {$_.name -eq $VM_object.hostName} | select name).name -eq $null) { $ResourceGroupName=$VM_object.resourceGroupName $locName=$VM_object.location $StorageAccountName=$VM_object.osDiskStorageAccountName $vmSize=$VM_object.roleSize $saResourceGroupName=$VM_object.saResourceGroup $saloc=$VM_object.saRegionName $satype=$VM_object.saType #creating resource group if it doesnot exist already try { if($ResourceGroupName.Trim() -ne "") { if((Find-AzureRmResourceGroup -ErrorAction Stop | Where-Object{$_.Name -eq $ResourceGroupName} | select Name).Name -eq $null) { Write-Verbose -Verbose "Creating Resource group $ResourceGroupName" $rgstatus=New-AzureRmResourceGroup -Name $ResourceGroupName -Location $locName } } else { Write-Verbose -Verbose "Resource group entry is blank vm proviioning exiting" $FinalVMDetails += $FinalVMDetail continue } } catch { Write-Verbose -Verbose "$ResourceGroupName Resource Group not found so VM creation exiting" [string](Get-Date) + ": $ResourceGroupName Resource Group not found so VM creation exiting" | Out-File -FilePath $ErrorLogFile -Append $FinalVMDetails += $FinalVMDetail continue } #Check if ResourceGroup provisioning is completed successfully. if((Get-AzureRmResourceGroup -Name $ResourceGroupName -Location $locName -ErrorAction Stop | select ProvisioningState).ProvisioningState -eq "Succeeded") { Write-Verbose -Verbose "Provisioning Status of the resource gropup $ResourceGroupName is Succeded" } else { ################## Failed Creating Resource group Write-Verbose -Verbose "Provisioning Status of the resource gropup $ResourceGroupName is"$rgstatus.ProvisioningState Write-Verbose -Verbose "Provisioning of VM $vmName exiting........" $FinalVMDetails += $FinalVMDetail continue } #creating storage account if the destination storage account is missing try { if($StorageAccountName.Trim() -ne "") { if((Get-AzureRmStorageAccountNameAvailability -Name $StorageAccountName -ErrorAction Stop).NameAvailable) { if((Find-AzureRmResourceGroup | Where-Object{$_.Name -eq $saResourceGroupName}|select Name).Name -eq $null) { Write-Verbose -Verbose "Creating Resource group $saResourceGroupName for storage account ......." $sargStatus=New-AzureRmResourceGroup -Name $saResourceGroupName -Location $locName if($sargStatus.ProvisioningState -eq "Succeeded") { Write-Verbose -Verbose "Provisioning State of Resource Group $saResourceGroupName is Succeeded " } else { Write-Verbose -Verbose "Provisioning State of Resource Group $saResourceGroupName is "$sargStatus.ProvisioningState } } Write-Verbose -Verbose "Creating Storage account $StorageAccountName ......." $sastatus=New-AzureRmStorageAccount -ResourceGroupName $saResourceGroupName -AccountName $StorageAccountName -Location $locName -Type $satype } } else { Write-Verbose "Storage Account entry is blank vm proviioning exitting" [string](Get-Date) + ": $StorageAccountName does not exist" | Out-File -FilePath $ErrorLogFile -Append $FinalVMDetails += $FinalVMDetail continue } } catch { Write-Verbose -Verbose "$StorageAccountName does not exist" [string](Get-Date) + ": $StorageAccountName does not exist" | Out-File -FilePath $ErrorLogFile -Append } #Check the Provisioning status of the Storage Account try { if((Get-AzureRmStorageAccount -Name $StorageAccountName -ResourceGroupName $saResourceGroupName -ErrorAction Stop | select ProvisioningState).ProvisioningState -eq "Succeeded") { Write-Verbose -Verbose "Provisioning Status of the Storage Account $StorageAccountName is Succeded" } else { Write-Verbose -Verbose "Provisioning Status of the Storage Account $StorageAccountName is"$sastatus.ProvisioningState [string](Get-Date) + ": $StorageAccountName could not be retrieved" | Out-File -FilePath $ErrorLogFile -Append } } catch { Write-Verbose -Verbose "$StorageAccountName could not be retrieved" [string](Get-Date) + ": $StorageAccountName could not be retrieved" | Out-File -FilePath $ErrorLogFile -Append $FinalVMDetails += $FinalVMDetail continue } ############################## VNET check ################################ try { $vnetName = ($VM_object.vnetName).Trim() if($vnetName -ne "") { $vnetchk=Get-AzureRMVirtualNetwork -ErrorAction Stop |Where-Object{$_.Name -eq $vnetName}|select -ExpandProperty Name if($vnetchk -eq "") { Write-Verbose -Verbose "Vnet $vnetName not found VM provisioning exitting" $FinalVMDetails += $FinalVMDetail continue } } else { Write-Verbose -Verbose "Vnet entry missing VM provisioning exitting" [string](Get-Date) + ": Vnet $vnetName not found VM provisioning exiting" | Out-File -FilePath $ErrorLogFile -Append $FinalVMDetails += $FinalVMDetail continue } } catch { Write-Verbose -Verbose "Vnet $vnetName not found VM provisioning exiting" [string](Get-Date) + ": Vnet $vnetName not found VM provisioning exiting" | Out-File -FilePath $ErrorLogFile -Append $FinalVMDetails += $FinalVMDetail continue } ################################################################################## try { $vnet=Get-AzureRMVirtualNetwork -Name $vnetName -ResourceGroupName $VM_object.vnetResourceGroupName -ErrorAction Stop ###################################### checking subnet and creating subnet ###################################### for($j=0;$j -le 1 ; $j++) { $subnetcreationstatus=-1 $vnetName=$VM_object.vnetName $subnetprop="subnet$j" $subnetname = $null $subnetname=$VM_object.$subnetprop if(($subnetname).Trim() -ne "") { $vnet=Get-AzureRMVirtualNetwork -Name $vnetName -ResourceGroupName $VM_object.vnetResourceGroupName -ErrorAction Stop $f=-1 foreach($subnet in $vnet.Subnets) { if($subnet.Name -eq $subnetname) { $f=1 break } else { $f=0 } } if($f -eq 0) { Write-Verbose -Verbose "Subnet $subnetname not found VM provisioning exitting" [string](Get-Date) + ": Subnet $subnetname not found VM provisioning exitting" + $_.Exception.Message | Out-File -FilePath $ErrorLogFile -Append $subnetcreationstatus=0 break } } } if($subnetcreationstatus -eq 0) { $FinalVMDetails += $FinalVMDetail continue } }################ TRy End catch { Write-Verbose -Verbose "$vnetName Virtual network not found from Resource group $vnet " [string](Get-Date) + ": $vnetName Virtual network not found from Resource group $vnet" | Out-File -FilePath $ErrorLogFile -Append } try { # Set the existing virtual network and subnet index $subnetIndex=1 $vnet=Get-AzureRMVirtualNetwork -Name $vnetName -ResourceGroupName $VM_object.vnetResourceGroupName -ErrorAction Stop $StaticIP1=$null $StaticIP2=$null $subnetid1=$null $subnetid2=$null $nic1=$null $nic2=$null if((($VM_object.ip1).trim() -ne "") -and (($VM_object.ip1).trim() -ne "Dynamic")) { $StaticIP1 = ($VM_object.ip1).Trim() } if((($VM_object.ip2).trim() -ne "") -and (($VM_object.ip2).trim() -ne "Dynamic")) { $StaticIP2 = ($VM_object.ip2).Trim() } ################################################################################################## # Create the NIC $nicName1="$vmName"+"nic0" $nicName2="$vmName"+"nic1" if(($VM_object.PublicIP).trim() -ieq "TRUE") { $pip = New-AzureRmPublicIpAddress -Name $nicName1 -ResourceGroupName $ResourceGroupName -Location $locName -AllocationMethod Dynamic -Force -ErrorAction Stop ####################################### Static IP Check ############################################# if(($VM_object.ip1).Trim() -ne "") { $subnetid1=$vnet.subnets | Where-Object {$_.name -eq $VM_object.subnet0} | select Id $nic1 = New-AzureRmNetworkInterface -Name $nicName1 -ResourceGroupName $ResourceGroupName -Location $locName -SubnetId $subnetid1.id -PrivateIpAddress $StaticIP1 -PublicIpAddressId $pip.Id -Force -ErrorAction Stop } if(($VM_object.ip2).trim() -ne "") { $subnetid2=$vnet.subnets | Where-Object {$_.name -eq $VM_object.subnet1} | select Id $nic2 = New-AzureRmNetworkInterface -Name $nicName2 -ResourceGroupName $ResourceGroupName -Location $locName -SubnetId $subnetid2.id -PrivateIpAddress $StaticIP2 -Force -ErrorAction Stop #-PublicIpAddressId $pip.Id } if($nic1.ProvisioningState -eq "Succeeded") { Write-Verbose -Verbose "Provisioning Status of Network Interface Card $nicName1 is Succeded" } if($nic2.ProvisioningState -eq "Succeeded") { Write-Verbose -Verbose "Provisioning Status of Network Interface Card $nicName2 is Succeded" } } else { if(($VM_object.ip1).Trim() -ne "") { $subnetid1=$vnet.subnets | Where-Object {$_.name -eq $VM_object.subnet0} | select Id $nic1 = New-AzureRmNetworkInterface -Name $nicName1 -ResourceGroupName $ResourceGroupName -Location $locName -SubnetId $subnetid1.Id -PrivateIpAddress $StaticIP1 -Force -ErrorAction Stop #-PublicIpAddressId $pip.Id } if(($VM_object.ip2).Trim() -ne "") { $subnetid2=$vnet.subnets | Where-Object {$_.name -eq $VM_object.subnet1} | select Id $nic2 = New-AzureRmNetworkInterface -Name $nicName2 -ResourceGroupName $ResourceGroupName -Location $locName -SubnetId $subnetid2.Id -PrivateIpAddress $StaticIP2 -Force -ErrorAction Stop } if($nic1.ProvisioningState -eq "Succeeded") { Write-Verbose -Verbose "Provisioning Status of Network Interface Card $nicName1 is Succeded" } if($nic2.ProvisioningState -eq "Succeeded") { Write-Verbose -Verbose "Provisioning Status of Network Interface Card $nicName2 is Succeded" } } } catch { Write-Verbose -Verbose "Error in creating NIC card creation" [string](Get-Date) + ": Error in creating NIC creation" + $_.Exception.message | Out-File -FilePath $ErrorLogFile -Append $FinalVMDetails += $FinalVMDetail continue } try { Write-Host "setting ip static for $nicName1" $b = Get-AzureRmNetworkInterface -ResourceGroupName $ResourceGroupName -Name $nicName1 $d = $b.IpConfigurations[0].privateipallocationmethod = "Static" $e = Set-AzureRmNetworkInterface -NetworkInterface $b Write-Verbose -Verbose "IP is static" } catch { Write-Verbose -Verbose "Error in setting ip static" } # Specify the name, size, and existing availability set if provided in the input files $avName=$VM_object.avset $avcretae_status=1 #Check if Availability Set was mentioned in the input file if($avName.Trim() -ne "") { try { $avSet=Get-AzureRmAvailabilitySet –ResourceGroupName $ResourceGroupName|Where-Object{$_.Name -eq $avName} -ErrorAction stop #Check if an existing Availability Set exists in the Azure susbscription with the same name as provided in the Inputfile. if not, create, else select the existing one if($avset -eq $null) { Write-Verbose -Verbose "Avalibilty set $avname doesnot Exist" Write-Verbose -verbose "Creating Availibilty set $avname ....." $avset=New-AzureRmAvailabilitySet -ResourceGroupName $ResourceGroupName -Name $avName -Location $locName -ErrorAction Stop if($avset -ne $null) { Write-Verbose -Verbose "Availiblity set $avname Created" } $vm = New-AzureRmVMConfig -VMName $vmName -VMSize $vmSize -AvailabilitySetId $avset.Id } else { $vm = New-AzureRmVMConfig -VMName $vmName -VMSize $vmSize -AvailabilitySetId $avset.Id } } catch { Write-Verbose -Verbose "Unexpected Error Occurs" [string](Get-Date) + "Unexpected Error Occurs" + $_.Exception.message | Out-File -FilePath $ErrorLogFile -Append $FinalVMDetails += $FinalVMDetail continue } } else {$avcretae_status = 0} if($avcretae_status -eq 0){ Write-Verbose -Verbose "VM creating without availibilty set" $vm = New-AzureRmVMConfig -VMName $vmName -VMSize $vmSize } #continue try { # Adding data disk if(($VM_object.hasDataDisk).Trim() -eq "TRUE") { #$disks=$DiskCSV|Where-Object{$_.hostName -eq $vmName} $diskcount = 0 $driveletter = ('F','G','H','I','J') if(($VM_object.diskSize0).Trim() -ne "") {$diskcount += 1} if(($VM_object.diskSize1).Trim() -ne "") {$diskcount += 1} if(($VM_object.diskSize2).Trim() -ne "") {$diskcount += 1} if(($VM_object.diskSize3).Trim() -ne "") {$diskcount += 1} if(($VM_object.diskSize4).Trim() -ne "") {$diskcount += 1} for($i=0; $i -lt $diskcount;$i++ ) { $diskSizename="diskSize$i" #$disklabelname = "diskLabel$i" #$drivelettername = $driveletter[$i] #$hostCachingname= "ReadWrite" $diskSize=$VM_object.$diskSizename $DiskLabelPropertyName = "diskLabel" + [string]($i) $diskLabel = "DataDisk" + [string]($i + 1) $diskName=$null $diskName=$driveletter[$i] $hostcaching="ReadWrite" $lun=$i if($diskName -ne $null) { $storageAcc=Get-AzureRmStorageAccount -ResourceGroupName $saResourceGroupName -Name $StorageAccountName -ErrorAction Stop $vhdURI=$storageAcc.PrimaryEndpoints.Blob.ToString() + "vhds/" + $vmName + $diskName + ".vhd" $diskinfo=$null $diskinfo=Add-AzureRmVMDataDisk -VM $vm -Name $diskLabel -DiskSizeInGB $diskSize -VhdUri $vhdURI -CreateOption empty -Caching $hostcaching -Lun $lun -ErrorAction Stop if($diskinfo -ne $null) { Write-Verbose -Verbose "disk $diskName attached" } else { Write-Verbose -Verbose "disk $diskName attach failed" } } } } else { Write-Verbose -Verbose "No disk to attach" [string](Get-Date) + ": No disk to attach" + $_.Exception.Message | Out-File -FilePath $ErrorLogFile -Append } } catch { Write-Verbose -Verbose "Disk Provisioning Failed" [string](Get-Date) + ": Disk Provisioning Failed" + $_.Exception.Message | Out-File -FilePath $ErrorLogFile -Append } ############################### To be Read ################################################# $UserPWD = $VM_object.adminUserPwd $secpasswd = ConvertTo-SecureString $UserPWD -AsPlainText -Force $UserName = $VM_object.adminUserName $cred = New-Object System.Management.Automation.PSCredential ($UserName,$secpasswd) $vm=Set-AzureRmVMOperatingSystem -VM $vm -Windows -ComputerName $vmName -Credential $cred -ProvisionVMAgent -EnableAutoUpdate -ErrorAction Stop # NOTE: Use the below code in case you want to use the existing Azure Image available #$vm=Set-AzureRmVMSourceImage -VM $vm -PublisherName $pubName -Offer $offerName -Skus $skuName -Version "latest" -ErrorAction Stop if(($VM_object.ip1).trim() -ne "") { $vm=Add-AzureRmVMNetworkInterface -VM $vm -Id $nic1.Id -ErrorAction Stop } $vm.NetworkProfile.NetworkInterfaces.Item(0).Primary = $true if(($VM_object.ip2).trim() -ne "") { $vm=Add-AzureRmVMNetworkInterface -VM $vm -Id $nic2.Id -ErrorAction Stop } # Specify the OS disk name and create the VM ##Set the VM's OS disk $BlobCopyJob = @() $RDestOsDiskUri = $VM_object.DestOsDiskUri $diskName="osdisk" $storageAcc = Get-AzureRmStorageAccount -ResourceGroupName $saResourceGroupName -Name $StorageAccountName -ErrorAction Stop $osDiskUri = '{0}vhds/{1}{2}.vhd' -f $storageAcc.PrimaryEndpoints.Blob.ToString(), $vmname.ToLower(), $diskName # NOTE: if you are deploying a Linux machine, replace the -Windows switch with a -Linux switch. $vm = Set-AzureRmVMOSDisk -VM $vm -Name $diskName -VhdUri $osDiskUri -CreateOption fromImage -SourceImageUri $RDestOsDiskUri -Windows -Caching None -ErrorAction Stop # NOTE: If you are deploying a virtual machine from an existing VHD which is NOT SYSPREP'd, # NOTE: uncomment one of the below options based on whether it is Windows or Linux based VM and comment the code above. #$vm=Set-AzureRmVMOSDisk -VM $vm -Name $diskName -VhdUri $RDestOsDiskUri -CreateOption "Attach" -Windows -Caching None -ErrorAction Stop #$vm=Set-AzureRmVMOSDisk -VM $vm -Name $diskName -VhdUri $RDestOsDiskUri -CreateOption "Attach" -Linux -Caching None -ErrorAction Stop ############################# Tag Logic ################################################## $tags = @() for($i=0;$i -lt 10;$i++) { $tagname = "tagname" + [string]$i $tagvalue = "tagvalue" + [string]$i if($VM_object.$tagname) { if(($VM_object.$tagname).trim() -ne "") { $tags += @( @{ Name=$VM_object.$tagname; Value=$VM_object.$tagvalue }) } } } ########################################################################################## $vmstatus = New-AzureRmVM -ResourceGroupName $ResourceGroupName -Location $locName -VM $vm -Tag $tags -ErrorAction Stop $newvm=Get-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $vmName -ErrorAction Stop if($VM_object.Bootdiagnostic -eq "TRUE") { Set-AzureRmDiagnosticSetting -ResourceId $newvm.id -Enabled $true # Use the below code to enable diagnostic settings for the VM #set-AzureRMVMDiagnosticsExtension -ResourceGroupName $ResourceGroupName -VMName $vmName -StorageAccountName $StorageAccountName } $newvm | Select -Property Name, ProvisioningState #$newvmwr [string](Get-Date) + " $vmName has been successfully provisioned storing VM detail in file !!!" | Out-File -FilePath $ErrorLogFile -Append $VMState = $newvm.ProvisioningState #$VMState $FinalVMDetail.Status = $VMState $IP = (Get-AzureRmNetworkInterface -ResourceGroupName $ResourceGroupName -Name $nicName1 -ErrorAction Stop).IpConfigurations.PrivateIpAddress # "Proceeding after the Nic Details" #$IP $FinalVMDetail.'IP Address' = $IP $FinalVMDetails += $FinalVMDetail } else { Write-Verbose -Verbose "Virtual Machine Named $vmName already exist" Write-Verbose -Verbose "Provisioning of VM $vmName exiting........" [string](Get-Date) + ": $vmName Provisioning failed !!!" + $_.Exception.Message | Out-File -FilePath $ErrorLogFile -Append $FinalVMDetails += $FinalVMDetail continue } } $FinalVMDetails | Export-csv -Path $Outputfile -NoTypeInformation -Force