Hello,
I referenced this LucD article in attempt to get my PowerCLI VM migration script to spawn jobs instead of running through clusters sequentially.
LucD notes |
remove preview |
 |
Running a background job - LucD notes |
Tweet PowerShell has a nifty feature that allows you to run code in [...] |
View this on LucD notes > |
|
|
I'm having some issues, maybe to do with passing variables? The jobs create successfully but finish immediately without doing anything.
I apologize for the massive code block. I assume the issue is within the $code ScriptBlock or the $sJob parameter block.
$ScriptName = "VM_Balance_and_Migration"
$Version = "1.1"
$LastModified = (Get-Item .).LastWriteTime
$Author = "Johnny Appleseed"
$DateFormat = "%m%d%Y_%H%M%S"
$DateTime = Get-Date -UFormat $DateFormat
$ScriptLogName = "$ScriptName"+"_Log_"+"$DateTime"
$ScriptLogPath = "C:\scripts\logs\$ScriptLogName.log"
$VerbosePreference = "Continue" # Default = "SilentlyContinue"
$ErrorActionPreference = "Continue" # Default = "Continue"
$WarningPreference = "Continue" # Default = "Continue"
$ErrorView = "NormalView" # Default = "NormalView"
$ConfirmPreference = "None" # Default = "High"
$PSDefaultParameterValues["Write-Host:ForegroundColor"] = "Green"
$PSDefaultParameterValues["Write-Host:BackgroundColor"] = "Black"
$Host.UI.RawUI.WindowTitle = "$ScriptName"
$White = @{ForegroundColor = "White"; BackgroundColor = "Black"}
$Cyan = @{ForegroundColor = "Cyan"; BackgroundColor = "Black"}
$Green = @{ForegroundColor = "Green"; BackgroundColor = "Black"}
$Yellow = @{ForegroundColor = "Yellow"; BackgroundColor = "Black"}
$Red = @{ForegroundColor = "Red"; BackgroundColor = "Black"}
$Blue = @{ForegroundColor = "Blue"; BackgroundColor = "Black"}
$Magenta = @{ForegroundColor = "Magenta"; BackgroundColor = "Black"}
Function Write-Log {
<#
.SYNOPSIS
Write-Log writes a message to a specified log file and/or the host with the current time stamp.
.DESCRIPTION
Author: Johnny Appleseed
Write-Log writes a message to a specified log file and/or the host with the current time stamp.
Uses "C:\scripts\logs\Default_Log.log" as the default log file if no path/file name is specified.
Define $ScriptLogPath variable in the script to change the default log location.
.PARAMETER Message
Message is the content that you wish to add to the log file.
.PARAMETER Level
Specify the criticality of the log information being written to the log (i.e. Error, Warning, Informational)
.PARAMETER LogPath
Specify a path and file name for the log file.
.PARAMETER NoHost
Generates log entries without outputting to the host.
.EXAMPLE
Write-Log -Message "Warning log message" -Level Warning -NoHost
Writes a warning message to the log file without outputting to the host.
.EXAMPLE
Write-Log "Folder does not exist." -LogPath "C:\Scripts\Logs\test1.log" -Level Error
Writes a message to the host and the log file located at "C:\Scripts\Logs\test1.log", and writes the message to the error pipeline.
.EXAMPLE
Write-Log "Informational message"
Writes the message to the host and logs it as informational in the default or predefined log file location.
.LINK
Inspired by:
https://www.techtarget.com/searchwindowsserver/tutorial/Build-a-PowerShell-logging-function-for-troubleshooting
#>
[CmdletBinding()]
Param (
[Parameter(
Mandatory=$true,
ValueFromPipeline=$true,
Position=0)]
[ValidateNotNullorEmpty()]
[String]$Message,
[Parameter(Position=1)]
[ValidateSet("Information","Warning","Error","Debug","Verbose")]
[String]$Level = 'Information',
[String]$LogPath = $ScriptLogPath,
[Switch]$NoHost
)
Begin {
if (!(Test-Path $LogPath)) {
$null = New-Item -Path $LogPath -Force
}
}
Process {
$DateFormat = "%m/%d/%Y %H:%M:%S"
If (-Not $NoHost) {
Switch ($Level) {
"Information" {
Write-Host ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)
}
"Warning" {
Write-Warning ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)
}
"Error" {
Write-Error ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)
}
"Debug" {
Write-Debug ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message) -Debug:$true
}
"Verbose" {
Write-Verbose ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message) -Verbose:$true
}
}
}
Add-Content -Path $LogPath -Value ("[{0}] ({1}) {2}" -F (Get-Date -UFormat $DateFormat), $Level, $Message)
}
}
# Print script information with Write-Host animation
$String = "Script: $ScriptName"
$StringLength = $String.Length
$Char = 0
do {
$DisplayChar = $String[$Char]
Write-Host -NoNewLine "$DisplayChar" @MTron
Start-Sleep -Milliseconds 35
$Char++
} until ($Char -eq $StringLength)
Write-Host ""
Start-Sleep 1
$String = "Version: $Version"
$StringLength = $String.Length
$Char = 0
do {
$DisplayChar = $String[$Char]
Write-Host -NoNewLine "$DisplayChar" @MTron
Start-Sleep -Milliseconds 35
$Char++
} until ($Char -eq $StringLength)
Write-Host ""
Start-Sleep 1
$String = "Last Modified: $LastModified"
$StringLength = $String.Length
$Char = 0
do {
$DisplayChar = $String[$Char]
Write-Host -NoNewLine "$DisplayChar" @MTron
Start-Sleep -Milliseconds 35
$Char++
} until ($Char -eq $StringLength)
Write-Host ""
Start-Sleep 1
$String = "Author: $Author"
$StringLength = $String.Length
$Char = 0
do {
$DisplayChar = $String[$Char]
Write-Host -NoNewLine "$DisplayChar" @MTron
Start-Sleep -Milliseconds 35
$Char++
} until ($Char -eq $StringLength)
Write-Host ""
Start-Sleep 1
# Print script start time and add log entry
Write-Log -Message "Script $ScriptName started."
$StartTime = Get-Date
# Connect to vCenter
$vCenter = "examplevcenter.net"
Write-Log "Connecting to vCenter $vCenter"
Connect-VIServer $vCenter
# Define input file path
$InputFilePath = "C:\scripts\karwosm\ClusterList.txt"
# Show contents of the input list, allow user to make changes until 'y' is entered
do {
Write-Host "`nPlease confirm that the text file located at '$InputFilePath' contains a full list of the store clusters on which to perform migrations, one per line. They must all be located in the connected vCenter: $vCenter`nCurrent list contents are displayed below:" @MattyIce
Get-Content $InputFilePath
$Answer1 = Read-Host "`nIf the above list is correct, enter 'y' to continue. Otherwise, update the file and enter 'n' to show file contents again."
} until ($Answer1 -eq "y")
# Set input file variable
$InputFile = Get-Content $InputFilePath
Write-Host "Migration options:" @Green
Write-Host "1 - Migrate all VM's to the 555 host and place the 666 host in maintenance mode" @Cyan
Write-Host "2 - Migrate all VM's to the 666 host and place the 555 host in maintenance mode" @Cyan
Write-Host "3 - Remove both hosts from maintenance mode and balance VM's across hosts (111/222 on 555, 333/444 on 666)" @Cyan
do {
$Answer2 = Read-Host "What migration actions would you like to perform on all clusters in the above input file? (Enter 1, 2, or 3)"
} until (($Answer2 -eq "1") -or ($Answer2 -eq "2") -or ($Answer2 -eq "3"))
if ($Answer2 -like "1") {
$Confirm = Read-Host "Migrate all VM's to the 555 host and place the 666 host into maintenance mode for all clusters in the above input list? Enter 'y' to confirm"
} elseif ($Answer2 -like "2") {
$Confirm = Read-Host "Migrate all VM's to the 666 host and place the 555 host into maintenance mode for all clusters in the above input list? Enter 'y' to confirm"
} elseif ($Answer2 -like "3") {
$Confirm = Read-Host "Remove both hosts from maintenance mode and balance VM's across hosts (111/222 on 555, 333/444 on 666) for all clusters in the above input list? Enter 'y' to confirm"
}
if ($Confirm -ne "y") {
Write-Log "Actions were not confirmed. Exiting script." -Level Error
throw
}
# Set progress bar variables
$TotalItems = $InputFile.count
$CurrentItem = 1
foreach ($I in $InputFile) {
$Cluster = Get-Cluster $I
$ClusterName = $Cluster.Name
# Check that the cluster is a standard store cluster (So that the script can be run in test vCenter)
$Check555 = $Cluster | Get-VMHost | where {$_.Name -like "*555*"}
$Check666 = $Cluster | Get-VMHost | where {$_.Name -like "*666*"}
$Check111 = $Cluster | Get-VM | where {$_.Name -like "*111*"}
$Check222 = $Cluster | Get-VM | where {$_.Name -like "*222*" -and $_.Name -notlike "*replica*"}
$Check333 = $Cluster | Get-VM | where {$_.Name -like "*333*"}
$Check444 = $Cluster | Get-VM | where {$_.Name -like "*444*"}
$Count111 = $Check111.count
$Count222 = $Check222.count
$Count333 = $Check333.count
$Count444 = $Check444.count
if ($Check555-and $Check666 -and $Check111 -and $Check222 -and $Check333 -and $Check444 -and $Count111 -eq "1" -and $Count222 -eq "1" -and $Count333 -eq"1" -and $Count444 -eq "1") {
# Display progress bar
Write-Progress -Activity "Starting migration job on cluster $ClusterName" -Status "Cluster $CurrentItem of $TotalItems" -PercentComplete (($CurrentItem/$TotalItems)*100)
$code = {
param(
[string]$Server,
[string]$SessionId,
[string]$Cluster,
[string]$Answer2
)
Set-PowerCLIConfiguration -DisplayDeprecationWarnings $false -Confirm:$false | Out-Null
Connect-VIServer -Server $Server -Session $SessionId
# Define cluster variables
$Host555 = $Cluster | Get-VMHost | where {$_.Name -like "*555*"}
$Host555Name = $Host555.name
$Host666 = $Cluster | Get-VMHost | where {$_.Name -like "*666*"}
$Host666Name = $Host666.name
$AllVMs = $Cluster | Get-VM | where {$_.Name -notlike "*replica*" -and $_.Name -notlike "*vCLS*"}
$555VMs = $Cluster | Get-VM | where {($_.Name -notlike "*replica*" -and $_.Name -notlike "*vCLS*") -and ($_.Name -like "*111*" -or $_.Name -like "*222*")}
$666VMs = $Cluster | Get-VM | where {($_.Name -notlike "*replica*" -and $_.Name -notlike "*vCLS*") -and ($_.Name -like "*333*" -or $_.Name -like "*444*")}
# Check that both hosts are powered on
if (($Host555.PowerState -eq "PoweredOn") -and ($Host666.PowerState -eq "PoweredOn")) {
# Start migration job
if ($Answer2 -like "1") {
# Check if host 555 is in maintenance mode, if so remove it from maintenance mode
$VMHostStatus = $Host555.State
if ($VMHostStatus -eq "Maintenance") {
$Host555 | Set-VMHost -State Connected
} else {}
# Migrate all VM's to host 555
$Relo555VMs = $AllVMs | where {$_.VMHost.Name -ne $Host555Name}
$Relo555VMs | Move-VM -Destination $Host555 -VMotionPriority High -RunAsync
# Place host 666 in maintenance mode
$VMHostStatus = $Host666.State
if ($VMHostStatus -eq "Maintenance") {
} else {
$spec = new-object VMware.Vim.HostMaintenanceSpec
$spec.VsanMode = new-object VMware.Vim.VsanHostDecommissionMode
$spec.VsanMode.ObjectAction = "ensureObjectAccessibility"
$Host666.ExtensionData.EnterMaintenanceMode(0, $false, $spec)
}
} elseif ($Answer2 -like "2") {
# Check if host 666 is in maintenance mode, if so remove it from maintenance mode
$VMHostStatus = $Host666.State
if ($VMHostStatus -eq "Maintenance") {
$Host666 | Set-VMHost -State Connected
} else {}
# Migrate all VM's to host 666
$Relo666VMs = $AllVMs | where {$_.VMHost.Name -ne $Host666Name}
$Relo666VMs | Move-VM -Destination $Host666 -VMotionPriority High -RunAsync
# Place host 555 in maintenance mode
$VMHostStatus = $Host555.State
if ($VMHostStatus -eq "Maintenance") {
} else {
$spec = new-object VMware.Vim.HostMaintenanceSpec
$spec.VsanMode = new-object VMware.Vim.VsanHostDecommissionMode
$spec.VsanMode.ObjectAction = "ensureObjectAccessibility"
$Host555.ExtensionData.EnterMaintenanceMode(0, $false, $spec)
}
} elseif ($Answer2 -like "3") {
# Remove both hosts from maintenance mode
$Host555 | Set-VMHost -State Connected
$Host666 | Set-VMHost -State Connected
# Balance VM's across both hosts
$555VMs | Move-VM -Destination $Host555 -VMotionPriority High -RunAsync
$666VMs | Move-VM -Destination $Host666 -VMotionPriority High -RunAsync
}
} else {}
}
$sJob = @{
ScriptBlock = $code
ArgumentList = $global:DefaultVIServer.Name, $global:DefaultVIServer.SessionId, $Cluster, $Answer2
}
Start-Job @sJob
$CurrentItem++
} else {
Write-Log "Cluster $ClusterName is not a store cluster. Skipping." -Level Error
}
}
# Print script end time and add log entry
$ElapsedTime = (Get-Date) - $StartTime
$TotalTime = "{0:HH:mm:ss}" -f ([datetime]$ElapsedTime.Ticks)
Write-Log "Script $ScriptName completed. Total run time: $TotalTime"
Write-Host "Log file located at $ScriptLogPath" @Green
# Disconnect from vCenter
Disconnect-VIServer