umms
Enthusiast
Enthusiast

Move VMs to Folders

Jump to solution

I have exported into CSV file the VMs and the Folder Paths in format:

Name                    Path

Not having much luck importing these to move the VMs to the folder structure (which is already in place). Can anyone give me some suggestions of Code to try? Thanks.

0 Kudos
1 Solution

Accepted Solutions
LucD
Leadership
Leadership

After some further analysis, the following script worked.

function Get-FolderByPath {

    <#

    .SYNOPSIS Retrieve folders by giving a path

    .DESCRIPTION The function will retrieve a folder by it's path.

    The path can contain any type of leave (folder or datacenter).

    .NOTES

    Author: Luc Dekens .PARAMETER Path The path to the folder. This is a required parameter.

    .PARAMETER

    Path The path to the folder. This is a required parameter.

    .PARAMETER

    Separator The character that is used to separate the leaves in the path. The default is '/'

    .EXAMPLE

    PS> Get-FolderByPath -Path "Folder1/Datacenter/Folder2"

    .EXAMPLE

    PS> Get-FolderByPath -Path "Folder1>Folder2" -Separator '>'

    #>

    param(

        [CmdletBinding()]

        [parameter(Mandatory = $true)]

        [System.String[]]${Path},

        [char]${Separator} = '/'

    )

    process {

        if ((Get-PowerCLIConfiguration).DefaultVIServerMode -eq "Multiple") {

            $vcs = $global:defaultVIServers

        }

        else {

            $vcs = $global:defaultVIServers[0]

        }

        foreach ($vc in $vcs) {

            $si = Get-View ServiceInstance -Server $vc

            $rootName = (Get-View -Id $si.Content.RootFolder -Property Name).Name

            foreach ($strPath in $Path) {

                $root = Get-Folder -Name $rootName -Server $vc -ErrorAction SilentlyContinue

                $strPath.Split($Separator) | ForEach-Object {

                    $root = Get-Inventory -Name $_ -Location $root -Server $vc -ErrorAction SilentlyContinue

                    if ((Get-Inventory -Location $root -NoRecursion | Select-Object -ExpandProperty Name) -contains "vm") {

                        $root = Get-Inventory -Name "vm" -Location $root -Server $vc -NoRecursion

                    }

                }

                $root | Where-Object { $_ -is [VMware.VimAutomation.ViCore.Impl.V1.Inventory.FolderImpl] } | ForEach-Object {

                    Get-Folder -Name $_.Name -Location $root.Parent -NoRecursion -Server $vc

                }

            }

        }

    }

}

  

Import-Module -Name VMware.PowerCLI

If ($globale:DefaultVIServers) {

    Disconnect-VIServer -Server $global:DefaultVIServers -Force -ErrorAction SilentlyContinue

}


$destVI = Read-Host "Please enter name or IP address of the vCenter"

$creds = Get-Credential -Message 'Enter credentials for $destVI'

$datacenter = Read-Host 'Please enter name of the Datacenter'

Connect-VIServer -server $destVI -Credential $creds


# move the vm's to correct location

Import-Csv "c:\csv-files\04-$($datacenter)-vms-with-FolderPath.csv" |

ForEach-Object -Process {

    $pathStr = $_.FolderPath.Replace("\$($_.VMName)",'').TrimStart('\')

    $folder = Get-FolderByPath -Path $pathStr -Separator '\'

    $vm = Get-VM -Name $_.VMName

    Move-VM -VM $vm -Destination $vm.VMHost -InventoryLocation $folder -Confirm:$false

}


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

View solution in original post

0 Kudos
55 Replies
LucD
Leadership
Leadership

It would help if you tell us in which format that folder path is in the CSV file.
And perhaps share the code you are using and which errors you are getting.

You might want to have a look at my Folder By Path post.
Once you have the Folder object, a simple Move-VM with the InventoryLocation parameter should do the trick.


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
umms
Enthusiast
Enthusiast

I will check out your page. I am very new to this!!

The CSV:

TestVM    \DC\TestFolder\SubTestFolder\

VM name is 1st colum and path is second column.

Code:

Get-Module -Name VMware* -ListAvailable | Import-Module
If ($globale:DefaultVIServers) {
Disconnect-VIServer -Server $global:DefaultVIServers -Force
}

$destVI = Read-Host "Please enter name or IP address of the destination Server"
$datacenter = Read-Host "DataCenter naam in VC"
$creds = get-credential
connect-viserver -server $destVI -Credential $creds

# move the vm's to correct location
$VMfolder = @()
$VMfolder = import-csv "c:\csv-files\04-$($datacenter)-vms-with-FolderPath.csv" | Sort-Object -Property Path

foreach($guest in $VMfolder){
$key = @()
$key =  Split-Path $guest.Path | split-path -leaf
if ($key -eq $datacenter) {
Write-Host "Root folder $guest.path"
##
Move-VM (Get-VM $guest.Name) -Destination "vm"
}
else
{
Move-VM (Get-VM $guest.Name) -Destination (Get-folder $key)
}
}

Disconnect-VIServer "*" -Confirm:$False

ERROR:

WARNING: Specifying Folder in the Destination parameter is deprecated and the parameter will stop accepting Folder
objects at a future release. Please use the InventoryLocation parameter instead.
Move-VM : 2/16/2020 12:35:58 AM Move-VM         Server task failed: The request refers to an unexpected or unknown type.
At C:\temp\CheapDisasterRecovery\!import-05-move-vms-folders_update.ps1:27 char:3
+         Move-VM (Get-VM $guest.Name) -Destination (Get-folder $key)
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Move-VM], VimException
    + FullyQualifiedErrorId : Security_Impl_TaskResultConverter_TaskNotSucceeded,VMware.VimAutomation.ViCore.Cmdlets.Commands.MoveVM

0 Kudos
LucD
Leadership
Leadership

You could try something like this

function Get-FolderByPath {

    <#

    .SYNOPSIS Retrieve folders by giving a path

    .DESCRIPTION The function will retrieve a folder by it's path.

    The path can contain any type of leave (folder or datacenter).

    .NOTES

    Author: Luc Dekens .PARAMETER Path The path to the folder. This is a required parameter.

    .PARAMETER

    Path The path to the folder. This is a required parameter.

    .PARAMETER

    Separator The character that is used to separate the leaves in the path. The default is '/'

    .EXAMPLE

    PS> Get-FolderByPath -Path "Folder1/Datacenter/Folder2"

    .EXAMPLE

    PS> Get-FolderByPath -Path "Folder1>Folder2" -Separator '>'

    #>

    param(

        [CmdletBinding()]

        [parameter(Mandatory = $true)]

        [System.String[]]${Path},

        [char]${Separator} = '/'

    )

    process {

        if ((Get-PowerCLIConfiguration).DefaultVIServerMode -eq "Multiple") {

            $vcs = $global:defaultVIServers

        }

        else {

            $vcs = $global:defaultVIServers[0]

        }

        foreach ($vc in $vcs) {

            $si = Get-View ServiceInstance -Server $vc

            $rootName = (Get-View -Id $si.Content.RootFolder -Property Name).Name

            foreach ($strPath in $Path) {

                $root = Get-Folder -Name $rootName -Server $vc -ErrorAction SilentlyContinue

                $strPath.Split($Separator) | ForEach-Object {

                    $root = Get-Inventory -Name $_ -Location $root -Server $vc -ErrorAction SilentlyContinue

                    if ((Get-Inventory -Location $root -NoRecursion | Select-Object -ExpandProperty Name) -contains "vm") {

                        $root = Get-Inventory -Name "vm" -Location $root -Server $vc -NoRecursion

                    }

                }

                $root | Where-Object { $_ -is [VMware.VimAutomation.ViCore.Impl.V1.Inventory.FolderImpl] } | ForEach-Object {

                    Get-Folder -Name $_.Name -Location $root.Parent -NoRecursion -Server $vc

                }

            }

        }

    }

}

  

Import-Module -Name VMware*

If ($globale:DefaultVIServers) {

    Disconnect-VIServer -Server $global:DefaultVIServers -Force -ErrorAction SilentlyContinue

}


$destVI = Read-Host "Please enter name or IP address of the vCenter"

$creds = Get-Credential -Message 'Enter credentials for $destVI'

$datacenter = Read-Host 'Please enter name of the Datacenter'

Connect-VIServer -server $destVI -Credential $creds


# move the vm's to correct location

Import-Csv "c:\csv-files\04-$($datacenter)-vms-with-FolderPath.csv" |

ForEach-Object -Process {

    Move-VM -VM $_.Name -InventoryLocation (Get-FolderByPath -Path ($_.Path.TrimStart('\')))

}


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
umms
Enthusiast
Enthusiast
So would I run this whole script to just import the VMs? 
0 Kudos
LucD
Leadership
Leadership

What do you mean?

The above script just moves VMs to the folder indicated in the CSV (and so did your original script).

Is this a new requirement?


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
umms
Enthusiast
Enthusiast
Just wanted to make sure. My inexperience in all this is showing 🙂
0 Kudos
umms
Enthusiast
Enthusiast

Errors when I run this. First error is when I first run the script before I enter in VC info:

Import-Module : The specified module 'VMware*' was not loaded because no valid module file was found in any module
directory.
At C:\temp\move_folders_vms.ps1:50 char:1
+ Import-Module -Name VMware*
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (VMware*:String) [Import-Module], FileNotFoundException
    + FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand

Errors after I enter in VC info:

Move-VM : 2/16/2020 10:02:54 AM Move-VM         Value cannot be found for the mandatory parameter VM
At C:\temp\move_folders_vms.ps1:66 char:5
+     Move-VM -VM $_.Name -InventoryLocation (Get-FolderByPath -Path ($ ...
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Move-VM], VimException
    + FullyQualifiedErrorId : Core_BaseCmdlet_UnknownError,VMware.VimAutomation.ViCore.Cmdlets.Commands.MoveVM

0 Kudos
LucD
Leadership
Leadership

Ok, let's check what is installed.

Run the following

Get-Module -Name VMware* -ListAvailable

If that doesn't return anything, then run

Get-Module -ListAvailable

And to check what PowerShell version you are using, check what the following returns

$PSVersionTable


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
umms
Enthusiast
Enthusiast
I attached the output. Thanks for the help on this!
0 Kudos
umms
Enthusiast
Enthusiast
I think I know what the problem might be. All the VMs wer put in "Discovered Virtual Machine" Folder. I am guessing all the VMs need to be in the root of the VMs and Folders view?
0 Kudos
LucD
Leadership
Leadership

No the location of the VMs shouldn't be an issue.

But the loading of the PowerCLI modules seems to be one.

Try this on its own

Import-Module -Name VMware*

Check if the modules are actually loaded with

Get-Module -Name VMware*

If they are not loaded, check what is in

$env:PSModulePath


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
umms
Enthusiast
Enthusiast

PS C:\temp> Import-Module -Name VMware *
Import-Module : A positional parameter cannot be found that accepts argument '*'.
At line:1 char:1
+ Import-Module -Name VMware *
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Import-Module], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand

 

PS C:\temp> Get-Module -Name VMware *
Get-Module : A positional parameter cannot be found that accepts argument '*'.
At line:1 char:1
+ Get-Module -Name VMware *
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-Module], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.GetModuleCommand

 

PS C:\temp> $env:PSModulePath
D:\Users\scoraccie\Documents\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules

0 Kudos
LucD
Leadership
Leadership

There is no blank between VMware and *


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
umms
Enthusiast
Enthusiast

PS C:\temp> Import-Module -Name VMware*
Import-Module : The specified module 'VMware*' was not loaded because no valid module file was found in any module
directory.
At line:1 char:1
+ Import-Module -Name VMware*
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (VMware*:String) [Import-Module], FileNotFoundException
    + FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand

PS C:\temp> Get-Module -Name VMware*

ModuleType Version    Name                                ExportedCommands
---------- -------    ----                                ----------------
Script     6.7.0.1... VMware.DeployAutomation             {Add-DeployRule, Add-ProxyServer, Add-ScriptBundle, Copy-D...
Script     6.7.0.1... VMware.ImageBuilder                 {Add-EsxSoftwareDepot, Add-EsxSoftwarePackage, Compare-Esx...
Manifest   11.5.0.... VMware.PowerCLI
Script     6.7.0.1... VMware.Vim
Script     11.5.0.... VMware.VimAutomation.Cis.Core       {Connect-CisServer, Disconnect-CisServer, Get-CisService}
Script     11.0.0.... VMware.VimAutomation.Cloud          {Add-CIDatastore, Connect-CIServer, Disconnect-CIServer, G...
Script     11.5.0.... VMware.VimAutomation.Common         {Get-Task, Stop-Task, Wait-Task}
Script     11.5.0.... VMware.VimAutomation.Core           {Add-PassthroughDevice, Add-VirtualSwitchPhysicalNetworkAd...
Script     11.5.0.... VMware.VimAutomation.Hcx            {Connect-HCXServer, Disconnect-HCXServer, Get-HCXAppliance...
Script     7.10.0.... VMware.VimAutomation.HorizonView    {Connect-HVServer, Disconnect-HVServer}
Script     11.3.0.... VMware.VimAutomation.License        Get-LicenseDataManager
Script     11.5.0.... VMware.VimAutomation.Nsxt           {Connect-NsxtServer, Disconnect-NsxtServer, Get-NsxtPolicy...
Script     11.5.0.... VMware.VimAutomation.Sdk            {Get-ErrorReport, Get-InstallPath, Get-PSVersion}
Script     11.0.0.... VMware.VimAutomation.Security       {Get-SecurityInfo, Get-VTpm, Get-VTpmCertificate, Get-VTpm...
Script     11.5.0.... VMware.VimAutomation.Srm            {Connect-SrmServer, Disconnect-SrmServer}
Script     11.5.0.... VMware.VimAutomation.Storage        {Add-KeyManagementServer, Add-VsanObjectToRepairQueue, Cop...
Script     1.3.0.0    VMware.VimAutomation.StorageUtility Update-VmfsDatastore
Script     11.2.0.... VMware.VimAutomation.Vds            {Add-VDSwitchPhysicalNetworkAdapter, Add-VDSwitchVMHost, E...
Script     11.5.0.... VMware.VimAutomation.Vmc            {Add-VmcSddcHost, Connect-Vmc, Disconnect-Vmc, Get-AwsAcco...
Script     10.0.0.... VMware.VimAutomation.vROps          {Connect-OMServer, Disconnect-OMServer, Get-OMAlert, Get-O...
Script     6.5.1.7... VMware.VumAutomation                {Add-EntityBaseline, Copy-Patch, Get-Baseline, Get-Complia...

0 Kudos
LucD
Leadership
Leadership

Ok, in the script I provided earlier, replace the line

Import-Module -Name VMware.*

with

Import-Module -Name VMware.PowerCLI

and try the script again.


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
umms
Enthusiast
Enthusiast
T
0 Kudos
umms
Enthusiast
Enthusiast
T
0 Kudos
umms
Enthusiast
Enthusiast

Getting different error now:

You cannot call a method on a null-valued expression.
At C:\temp\move_folders_vms.ps1:66 char:5
+     Move-VM -VM $_.Name -InventoryLocation (Get-FolderByPath -Path ($ ...
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

0 Kudos
LucD
Leadership
Leadership

It looks like the CSV file might have a problem.

What does the following return?

$datacenter = Read-Host 'Please enter name of the Datacenter'

Import-Csv "c:\csv-files\04-$($datacenter)-vms-with-FolderPath.csv"


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos