Forum Discussion
Shehzad
Oct 01, 2023Copper Contributor
strange behavior when selecting multiple values in GridView
I was writing a script to export Hyper-V VM, the script is basically meant to be run from CLI or scheduled-tasks. but I like to make my script also able to be used interactively. In Scre...
Shehzad
Oct 02, 2023Copper Contributor
You are right that the script can only be executed on a Hyper-V Host (or remoting into Hyper-V host) and that's exactly what I am doing, I am runningit on Hyper-V Host. I have no issues with the command Get-VM, it is giving me a list of VM-Objects and I am extracting the names using (Get-VM).Name
LainRobertson
Oct 02, 2023Silver Contributor
I still cannot make Out-GridView do what you suspect it of doing - which is actually a good thing, so I've taken a look at your script and provided an alternative below.
It's not identical but similar and maybe gives you some ideas for your own script.
The version I'd dropped below:
- Can work locally on a hypervisor or remotely (so long as you have the Hyper-V tools components installed);
- Leverages the fact that the -Name parameter from the native Export-VM command is actually a string array type, not just a string (so I've skipped the per virtual machine looping altogether, which is more efficient).
Anyhow, it's just something else to review given I can't find any issues at all with Out-GridView.
[CmdletBinding()]
Param (
[Parameter()][string[]] $VMName # Name of the VM to export
, [Parameter()][string] $Path = "D:\Data\Temp\Forum\Export" # Path to export VM to
, [Parameter()][switch] $AppendDate # Whether to append datetime to export path
, [Parameter()][string] $ComputerName # The hypervisor you want to connect to
)
function Export-VirtualMachine
{
Param (
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string[]] $Name # Name of the VM to export
, [Parameter()][string] $Path = "D:\Export" # Path to export VM to
, [Parameter()][string] $ComputerName # The hypervisor you want to connect to
)
try
{
if ([string]::IsNullOrWhiteSpace($ComputerName))
{
Export-VM -Name $Name -Path $Path;
}
else
{
Export-VM -ComputerName $ComputerName -Name $Name -Path $Path;
}
}
catch
{
throw;
}
}
#region Set up some variables.
$Err_NoMachines = "No virtual machine names found. Aborting."
$Timestamp = [datetime]::Now.ToString("yyyyMMdd-HHmmss");
$Names = [System.Collections.Generic.List[string]]::new();
#region
try
{
#region Validate the parameters.
if ([string]::IsNullOrWhiteSpace($Path))
{
# We'd only be in here if someone deliberately passed "-Path $null" into the script. Unlikely but possible.
throw "-Path cannot be null.";
}
if ($AppendDate.IsPresent)
{
if ($Path.EndsWith("\"))
{
$Path = $Path.Substring(0, $Path.Length-1);
}
$Path += "-$Timestamp";
}
foreach ($Entry in $VMName)
{
if (-not [string]::IsNullOrWhiteSpace($Entry))
{
$Names.Add($Entry);
}
}
#endregion
if ($Names.Count -gt 0)
{
# Being in this block means we have names passed in from the calling process. We get to play dumb here as it's up to the caller to have ensured the $VMName and $ComputerName parameters are correct.
Export-VirtualMachine -ComputerName $ComputerName -Path $Path -Name $VMName -ErrorAction:Stop;
}
else
{
# Being in this block means no names were passed in from the calling process.
#region Fetch the virtual machine names from the nominated $ComputerName (or local host if $ComputerName is empty). Note: We're not splitting on commas as it's one name per line, not a character-separated single line.
$AllNames = [System.Collections.Generic.List[string]]::new();
if (-not [string]::IsNullOrWhiteSpace($ComputerName))
{
(Get-VM -ComputerName $ComputerName -ErrorAction:Stop).Name |
ForEach-Object {
$AllNames.Add($_);
};
}
else
{
(Get-VM -ErrorAction:Stop).Name |
ForEach-Object {
$AllNames.Add($_);
};
}
if (0 -eq $AllNames.Count)
{
Write-Warning -Message $Err_NoMachines;
return;
}
$AllNames.Sort();
$AllNames.Insert(0, "All"); # Cram a value of "All" at the start.
# Prompt the user to choose.
$Names = [string[]] ($AllNames | Out-GridView -OutputMode Multiple); # Notice here that we're forcing a typecast of a string array ([string[]]). This avoids unwieldly "if..else" checking.
if (0 -eq $Names.Count)
{
Write-Warning -Message $Err_NoMachines;
return;
}
elseif ($Names.Contains("All"))
{
$AllNames.RemoveAt(0);
$Names = $AllNames;
}
#endregion
if (-not [string]::IsNullOrWhiteSpace(($NewPath = (Read-Host -Prompt "Enter an export path or leave empty to use the default ($Path)"))))
{
$Path = $NewPath;
}
Export-VirtualMachine -ComputerName $ComputerName -Path $Path -Name $Names -ErrorAction:Stop;
}
}
catch
{
# We could perform some other actions here, but for this example, we're just going to re-throw the exception up to the caller and let them worry about it.
throw;
}
Edit #1: To include the "All" option handling (which I'd missed in the original.)
Edit #2: Correction on line 61 where I'd missed "-not".
Cheers,
Lain