VMware Cloud Community
piercj2
Enthusiast
Enthusiast
Jump to solution

Delete VMs and output results

I'm trying to write something that

1) verifies a VM is powered off (for more that 30 days)

2) queries an external DB to make sure it's safe to delete the VM

3) output the results to a different DB (don't have this DB yet so outputting to Excel for now)

I found Luc Deken's "Get-VMLog" code and, it has formed the basis for what i'm trying to do.

To keep the code understandable, I've broken it into various functions, i'm hoping this will also allow me add to it in time as needed.

I'm having trouble with scoped of Variables as a result and, don't understand scopes well enough to correct the issues i'm having.

any help would be appreciated. Here's what i've come up with so far

<#

.SYNOPSIS

Retrieve the virtual machine logs to identify how long they are Powered down

Query external DB to verify safe to delete VM

.DESCRIPTION

The function retrieves the logs from one or more virtual machines and stores them in a local folder

Date of last Log entry is captured

.NOTES

Credits:  Luc Dekens

#>

Set-PowerCLIConfiguration -DefaultVIServerMode Single -InvalidCertificateAction Ignore -Scope Session -Confirm:$False

function Install-PowerCLI {

    If (!(Get-Module -Name VMware.VimAutomation.Cis.Core)) {

        find-Module -Name VMware.VimAutomation.Cis.Core | Install-Module -Scope CurrentUser -Confirm:$false

    }

}

function Login-VCSA {

    Disconnect-VIServer * -Confirm:$False -ErrorAction SilentlyContinue | Out-Null

    $targetVC = Read-Host "Enter the FQDN of the vCenter you want to connect to"

    Connect-VIServer $targetVC | Out-Null -ErrorAction SilentlyContinue

    if($DefaultVIServer -eq $null) {

        Write-Host -ForegroundColor Red "Error: You need to connect to your vSphere environment before running this. Please do so now and try again"

        exit

    }

}

# Create folder to store results

function Create-Directory {

    $Global:directory = $null

    $Global:directory = Read-Host "Enter local directory to store results"

    if ( !(Test-Path -LiteralPath $directory)) {

        try {

            New-Item -Path $directory -ItemType Directory -ErrorAction Stop | Out-Null

        }

        catch {

            Write-Error -Message "Unable to create directory '$directory'. Error was: $_" -ErrorAction Stop

        }

        Write-Host -ForegroundColor Green "Successfully created directory '$directory'."

        } else {

            Write-Host -ForegroundColor Yellow "Directory '$directory' already exists - not creating it again"

        }

}

# Create text file that contains VM Names

function Create-File {

    $Global:file  = $null

    $Global:file = $directory + "\" + "vmList.txt"

    if ( !(Test-Path -LiteralPath $file -PathType Leaf)) {

        Set-Content -Encoding UTF8 -LiteralPath $file -Value ""

        Write-Host -ForegroundColor Green "Successfully created '$file'."

    } else {

        Write-Host -ForegroundColor Yellow "File '$file' already exists - not creating it again"

    }

    Write-Host "Enter VM Names into the text file and save it"

    start-sleep -Seconds 3

    ii $file

}

# install ImportExcel module if not already installed and prepare layout

function global:Prepare-Excel {

    If (!(Get-module -ListAvailable "ImportExcel")) {

        Find-Module -Name ImportExcel | Install-Module -Scope CurrentUser

    }

    $ContainsBlanks = New-ConditionalText -ConditionalType ContainsBlanks

    $excelHash = @{

        Path = $directory + "\DecomList-" + (Get-Date -Format yyyy-MMM-dd-HHmm) + ".xlsx"

        Show = $true;

        AutoSize = $true;

        AutoFilter = $true;

        ConditionalText = $ContainsBlanks

        ShowPercent = $true;

        HideSheet = "Sheet1";

    }

}

function Get-CIStatus {

    # Placeholder for when read-only access to CMDB is available

    # $ciStatus = identify CI Status from Asset Management SQL DB

}

function Get-Details {

    Read-Host "When you have saved the text file, press ENTER to continue..."

    $global:decomReport = @()

    $global:dir = $directory

    $VMList = get-vm (Get-Content $file)

    foreach($vm in $VMList){

        $powerState = $vm.PowerState

        $vmProperty = [ordered] @{

            'vCenter' = $global:DefaultVIServer.Name

            'VM Name' = $vm.Name

            'PowerState' = $powerState

        }

        # If VM Powered off Get the vmware.log file for the VM

        if ($powerState -eq "PoweredOff") {

        $logPath = $vm.Extensiondata.Config.Files.LogDirectory

        $dsName = $logPath.Split(']')[0].Trim('[')

        $vmPath = $logPath.Split(']')[1].Trim(' ')

        $ds = Get-Datastore -Name $dsName

        $drvName = "LogCollect-" + (Get-Random)

        New-PSDrive -Location $ds -Name $drvName -PSProvider VimDatastore -Root '\' | Out-Null

            Copy-DatastoreItem -Item ($drvName + ":" + $vmPath + "vmware.log") -Destination ($dir + "\" + $vm.Name + "\") -Force:$true

            $lastLine = Get-Content ($dir + "\" + $vm.Name + "\" + "vmware.log") -Tail 1

            [datetime]$LastLogDate = $lastLine.Split("|")[0]

            $vmProperty.Add("Date of last Log entry",$LastLogDate)

            $vmProperty.Add("Can be deleted","")

            $vmProperty.Add("Removed from Inventory",(Get-Date -Format yyyy-MMM-dd-HHmm))

        }

        else {

            $vmProperty.Add("Date of last Log entry",$LastLogDate)

            $vmProperty.Add("Can be deleted","NO")

            $vmProperty.Add("Removed from Inventory","NO - Notify Decom Team - Not Powered Off")

        }

           

        Remove-PSDrive -Name $drvName -Confirm:$false

        $decomReport += New-Object -TypeName psobject -Property $vmProperty

    }

    $decomReport |

        Sort-Object -Property 'VM Name' |

         Export-Excel @excelHash

         #Export-Csv ($directory + "\TempCSV.csv") -NoTypeInformation

}

function Remove-Decom {

    # Placeholder to delete Decommissioned VM's once pre-req's are met

    <#

    if ($ciStatus -eq "Decommissioned") {}

    #>

    <#

    # AND if (Has the VM been powered down for more than 30 days) {

        $target = (Get-Date).AddDays(-30)

        if ($LastLogDate -le $target) {

            Write-Host -ForegroundColor Red "'$vm.Name' has neen powered for more than 30 days"

        }

        else {

            Write-Host "$vm not powered down long enough, staying"

        }

    #>

    # Delete VM From Inventory

}

Install-PowerCLI

Login-VCSA

Create-Directory

Create-File

Prepare-Excel

Get-CIStatus

Get-Details

Tags (2)
0 Kudos
1 Solution

Accepted Solutions
LucD
Leadership
Leadership
Jump to solution

Looks like your New-PSDrive fails (sometimes), and then the subsequent Remove-PSDrive gives an error because the drive doesn't exist.

Try removing the pipe to Out-Null and perhaps add the Verbose switch on the New-PSdrive.

An alternative could be to use a try-catch

try{

    New-PSDrive ... -ErrorAction Stop

}

catch{

    throw "Error in creating the PSDrive"

}


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

View solution in original post

0 Kudos
4 Replies
LucD
Leadership
Leadership
Jump to solution

The ideal way to pass information between functions is to use parameters and function output.

Another option is to use variables with a Scope higher than Local.

Since this all happens in 1 script, you don't even need to Global scope.

The Script scope will suffice.

Note that a variable $Script:var is not the same a local variable $var in a function.

Also when referencing a variable, you have to specify the Scope (exception is the implicit Local Scope).

I think I made most required changes to use Script scope variables where needed.

Give it a try

<#

.SYNOPSIS

Retrieve the virtual machine logs to identify how long they are Powered down

Query external DB to verify safe to delete VM

.DESCRIPTION

The function retrieves the logs from one or more virtual machines and stores them in a local folder

Date of last Log entry is captured

.NOTES

Credits:  Luc Dekens

#>

Set-PowerCLIConfiguration -DefaultVIServerMode Single -InvalidCertificateAction Ignore -Scope Session -Confirm:$False

function Install-PowerCLI {

    If (!(Get-Module -Name VMware.VimAutomation.Cis.Core)) {

        find-Module -Name VMware.VimAutomation.Cis.Core | Install-Module -Scope CurrentUser -Confirm:$false

    }

}

function Login-VCSA {

    Disconnect-VIServer * -Confirm:$False -ErrorAction SilentlyContinue | Out-Null

    $targetVC = Read-Host "Enter the FQDN of the vCenter you want to connect to"

    Connect-VIServer $targetVC | Out-Null -ErrorAction SilentlyContinue

    if($DefaultVIServer -eq $null) {

        Write-Host -ForegroundColor Red "Error: You need to connect to your vSphere environment before running this. Please do so now and try again"

        exit

    }

}

# Create folder to store results

function Create-Directory {

    $Script:directory = $null

    $Script:directory = Read-Host "Enter local directory to store results"

    if ( !(Test-Path -LiteralPath $Script:directory)) {

        try {

            New-Item -Path $Script:directory -ItemType Directory -ErrorAction Stop | Out-Null

        }

        catch {

            Write-Error -Message "Unable to create directory '$Script:directory'. Error was: $_" -ErrorAction Stop

        }

        Write-Host -ForegroundColor Green "Successfully created directory '$Script:directory'."

        } else {

            Write-Host -ForegroundColor Yellow "Directory '$Script:directory' already exists - not creating it again"

        }

}

# Create text file that contains VM Names

function Create-File {

    $Script:file  = $null

    $Script::file = $Script:directory + "\" + "vmList.txt"

    if ( !(Test-Path -LiteralPath $Script:file -PathType Leaf)) {

        Set-Content -Encoding UTF8 -LiteralPath $Script:file -Value ""

        Write-Host -ForegroundColor Green "Successfully created '$Script:file'."

    } else {

        Write-Host -ForegroundColor Yellow "File '$Script:file' already exists - not creating it again"

    }

    Write-Host "Enter VM Names into the text file and save it"

    start-sleep -Seconds 3

    Invoke-Item $Script:file

}

# install ImportExcel module if not already installed and prepare layout

function global:Prepare-Excel {

    If (!(Get-module -ListAvailable "ImportExcel")) {

        Find-Module -Name ImportExcel | Install-Module -Scope CurrentUser

    }

    $ContainsBlanks = New-ConditionalText -ConditionalType ContainsBlanks

    $excelHash = @{

        Path = $Script:directory + "\DecomList-" + (Get-Date -Format yyyy-MMM-dd-HHmm) + ".xlsx"

        Show = $true;

        AutoSize = $true;

        AutoFilter = $true;

        ConditionalText = $ContainsBlanks

        ShowPercent = $true;

        HideSheet = "Sheet1";

    }

}

function Get-CIStatus {

    # Placeholder for when read-only access to CMDB is available

    # $ciStatus = identify CI Status from Asset Management SQL DB

}

function Get-Details {

    Read-Host "When you have saved the text file, press ENTER to continue..."

    $Script:decomReport = @()

    $Script:dir = $Script:directory

    $VMList = Get-VM (Get-Content $Script:file)

    foreach($vm in $VMList){

        $powerState = $vm.PowerState

        $vmProperty = [ordered] @{

            'vCenter' = $global:DefaultVIServer.Name

            'VM Name' = $vm.Name

            'PowerState' = $powerState

        }

        # If VM Powered off Get the vmware.log file for the VM

        if ($powerState -eq "PoweredOff") {

        $logPath = $vm.Extensiondata.Config.Files.LogDirectory

        $dsName = $logPath.Split(']')[0].Trim('[')

        $vmPath = $logPath.Split(']')[1].Trim(' ')

        $ds = Get-Datastore -Name $dsName

        $drvName = "LogCollect-" + (Get-Random)

        New-PSDrive -Location $ds -Name $drvName -PSProvider VimDatastore -Root '\' | Out-Null

            Copy-DatastoreItem -Item ($drvName + ":" + $vmPath + "vmware.log") -Destination ($dir + "\" + $vm.Name + "\") -Force:$true

            $lastLine = Get-Content ($dir + "\" + $vm.Name + "\" + "vmware.log") -Tail 1

            [datetime]$LastLogDate = $lastLine.Split("|")[0]

            $vmProperty.Add("Date of last Log entry",$LastLogDate)

            $vmProperty.Add("Can be deleted","")

            $vmProperty.Add("Removed from Inventory",(Get-Date -Format yyyy-MMM-dd-HHmm))

        }

        else {

            $vmProperty.Add("Date of last Log entry",$LastLogDate)

            $vmProperty.Add("Can be deleted","NO")

            $vmProperty.Add("Removed from Inventory","NO - Notify Decom Team - Not Powered Off")

        }

         

        Remove-PSDrive -Name $drvName -Confirm:$false

        $decomReport += New-Object -TypeName psobject -Property $vmProperty

    }

    $decomReport |

        Sort-Object -Property 'VM Name' |

         Export-Excel @excelHash

         #Export-Csv ($directory + "\TempCSV.csv") -NoTypeInformation

}

function Remove-Decom {

    # Placeholder to delete Decommissioned VM's once pre-req's are met

    <#

    if ($ciStatus -eq "Decommissioned") {}

    #>

    <#

    # AND if (Has the VM been powered down for more than 30 days) {

        $target = (Get-Date).AddDays(-30)

        if ($LastLogDate -le $target) {

            Write-Host -ForegroundColor Red "'$vm.Name' has neen powered for more than 30 days"

        }

        else {

            Write-Host "$vm not powered down long enough, staying"

        }

    #>

    # Delete VM From Inventory

}

Install-PowerCLI

Login-VCSA

Create-Directory

Create-File

Prepare-Excel

Get-CIStatus

Get-Details


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

0 Kudos
piercj2
Enthusiast
Enthusiast
Jump to solution

Thank you Luc,

i'll give this a go when back in the office tomorrow.

Regards,

Jason

0 Kudos
piercj2
Enthusiast
Enthusiast
Jump to solution

Thanks Luc,

That was a great help. I added in a few more VM Properties and a progress bar.

I needed to change some more variables but, it's working as expected now (but for one error that doesn't seem to impact the results)

sometimes, i get an error about removing a PSDrive, but the script still works.

the error is

Remove-PSDrive : Cannot find drive. A drive with the name 'LogCollect-103617767' does not exist.

At C:\Temp\Remove-VMs.ps1:140 char:9

+         Remove-PSDrive -Name $drvName -Confirm:$false | Out-Null

+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : ObjectNotFound: (LogCollect-103617767:String) [Remove-PSDrive], DriveNotFoundException

    + FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.RemovePSDriveCommand

The updated code is below

<#

.SYNOPSIS

Retrieve the virtual machine logs to identify how long they are Powered down

Query external DB to verify safe to delete VM

.DESCRIPTION

The function retrieves the logs from one or more virtual machines and stores them in a local folder

Date of last Log entry is captured

.NOTES

Credits:  Luc Dekens

#>

Set-PowerCLIConfiguration -DefaultVIServerMode Single -InvalidCertificateAction Ignore -Scope Session -Confirm:$False | Out-Null

function Install-PowerCLI {

    If (!(Get-Module -Name VMware.VimAutomation.Cis.Core)) {

        find-Module -Name VMware.VimAutomation.Cis.Core | Install-Module -Scope CurrentUser -Confirm:$false

    }

}

function Login-VCSA {

    Disconnect-VIServer * -Confirm:$False -WarningAction SilentlyContinue

    $targetVC = Read-Host "Enter the FQDN of the vCenter you want to connect to"

    Connect-VIServer $targetVC | Out-Null -ErrorAction SilentlyContinue

    if($DefaultVIServer -eq $null) {

        Write-Host -ForegroundColor Red "Error: You need to connect to your vSphere environment before running this. Please do so now and try again"

        exit

    }

}

# Create folder to store results

function Create-Directory {

    $Script:directory = $null

    $Script:directory = Read-Host "Enter local directory to store results"

    if ( !(Test-Path -LiteralPath $Script:directory)) {

        try {

            New-Item -Path $Script:directory -ItemType Directory -ErrorAction Stop | Out-Null

        }

        catch {

            Write-Error -Message "Unable to create directory '$Script:directory'. Error was: $_" -ErrorAction Stop

        }

        Write-Host -ForegroundColor Green "Successfully created directory '$Script:directory'."

        } else {

            Write-Host -ForegroundColor Yellow "Directory '$Script:directory' already exists - not creating it again"

        }

}

# Create text file that contains VM Names

function Create-File {

    $Script:file  = $null

    $Script:file = $Script:directory + "\" + "vmList.txt"

    if ( !(Test-Path -LiteralPath $Script:file -PathType Leaf)) {

        Set-Content -Encoding UTF8 -LiteralPath $Script:file -Value ""

        Write-Host -ForegroundColor Green "Successfully created '$Script:file'."

    } else {

        Write-Host -ForegroundColor Yellow "File '$Script:file' already exists - not creating it again"

    }

    Write-Host "Enter VM Names into the text file and save it. Opening File for you now."

    start-sleep -Seconds 3

    Invoke-Item $Script:file

}

# install ImportExcel module if not already installed and prepare layout

#function global:Prepare-Excel {

function script:Prepare-Excel {

    If (!(Get-module -ListAvailable "ImportExcel")) {

        Find-Module -Name ImportExcel | Install-Module -Scope CurrentUser

    }

    $cannotBeDeleted = New-ConditionalText -Text PoweredOn -BackgroundColor Yellow -ConditionalTextColor Red

    $Script:excelHash = @{

        Path = $Script:directory + "\DecomList-" + (Get-Date -Format yyyy-MMM-dd-HHmm) + ".xlsx"

        Show = $true;

        AutoSize = $true;

        AutoFilter = $true;

        ConditionalText = $cannotBeDeleted

        ShowPercent = $true;

        HideSheet = "Sheet1";

    }

}

function Get-CIStatus {

    # Placeholder for when read-only access to CMDB is available

    # $ciStatus = identify CI Status from Asset Management SQL DB

}

function Get-Details {

    Start-Sleep -Seconds 5

    Read-Host "When you have saved the text file, press ENTER to continue..."

    $Script:decomReport = @()

    $dir = $Script:directory

    #$Script:dir = $Script:directory

    $VMList = Get-VM (Get-Content $Script:file) | Sort-Object

    $count = $VMList.count

    $i = 1

    foreach($vm in $VMList){

        Write-Progress -Activity "Collecting details on provided VMs" -Status "Working on $vm" -PercentComplete (($i*100)/$count)

        $ip = $vm.guest.IPAddress[0]

        $powerState = $vm.PowerState

        $memory = $vm.memoryGB

        $hddSize = [math]::Round(((Get-HardDisk -VM $vm).CapacityGB | Measure-Object -Sum).Sum)

        $vmProperty = [ordered] @{

            'vCenter' = $global:DefaultVIServer.Name

            'VM Name' = $vm.Name

            'IP Address' = $ip

            'PowerState' = $powerState

            'Memory (GB)' = $memory

            'Disk Capacity (GB)' = $hddSize

        }

        # If VM Powered off Get the vmware.log file for the VM

        if ($powerState -eq "PoweredOff") {

        $logPath = $vm.Extensiondata.Config.Files.LogDirectory

        $dsName = $logPath.Split(']')[0].Trim('[')

        $vmPath = $logPath.Split(']')[1].Trim(' ')

        $ds = Get-Datastore -Name $dsName

        $drvName = "LogCollect-" + (Get-Random)

        New-PSDrive -Location $ds -Name $drvName -PSProvider VimDatastore -Root '\' | Out-Null

            Copy-DatastoreItem -Item ($drvName + ":" + $vmPath + "vmware.log") -Destination ($dir + "\" + $vm.Name + "\") -Force:$true

            #Copy-DatastoreItem -Item ($drvName + ":" + $vmPath + "vmware.log") -Destination ($Script:dir + "\" + $vm.Name + "\") -Force:$true

            $lastLine = Get-Content ($dir + "\" + $vm.Name + "\" + "vmware.log") -Tail 1

            #$lastLine = Get-Content ($Script:dir + "\" + $vm.Name + "\" + "vmware.log") -Tail 1

            [datetime]$LastLogDate = $lastLine.Split("|")[0]

            $vmProperty.Add("Date of last Log entry",$LastLogDate)

            $vmProperty.Add("Can be deleted","If all criteria met")

            $vmProperty.Add("Removed from Inventory",(Get-Date -Format yyyy-MMM-dd-HHmm))

        }

        else {

            $vmProperty.Add("Date of last Log entry",$LastLogDate)

            $vmProperty.Add("Can be deleted","NO")

            $vmProperty.Add("Removed from Inventory","NO - Notify Decom Team - Not Powered Off")

        }

        Remove-PSDrive -Name $drvName -Confirm:$false | Out-Null

        #$decomReport += New-Object -TypeName psobject -Property $vmProperty

        $Script:decomReport += New-Object -TypeName psobject -Property $vmProperty

        $i++

    }

    #$decomReport |

    $Script:decomReport |

        Sort-Object -Property 'VM Name' |

         Export-Excel @Script:excelHash

         #Export-Csv ($Script:directory + "\TempCSV.csv") -NoTypeInformation

}

function Remove-Decom {

    # Placeholder to delete Decommissioned VM's once pre-req's are met

    <#

    if ($ciStatus -eq "Decommissioned") {}

    #>

    <#

    # AND if (Has the VM been powered down for more than 30 days) {

        $target = (Get-Date).AddDays(-30)

        if ($LastLogDate -le $target) {

            Write-Host -ForegroundColor Red "'$vm.Name' has neen powered for more than 30 days"

        }

        else {

            Write-Host "$vm not powered down long enough, staying"

        }

    #>

    # Delete VM From Inventory

}

Install-PowerCLI

Login-VCSA

Create-Directory

Create-File

Script:Prepare-Excel

Get-CIStatus

Get-Details

0 Kudos
LucD
Leadership
Leadership
Jump to solution

Looks like your New-PSDrive fails (sometimes), and then the subsequent Remove-PSDrive gives an error because the drive doesn't exist.

Try removing the pipe to Out-Null and perhaps add the Verbose switch on the New-PSdrive.

An alternative could be to use a try-catch

try{

    New-PSDrive ... -ErrorAction Stop

}

catch{

    throw "Error in creating the PSDrive"

}


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

0 Kudos