Nicht immer und überall gibt es den Luxus des System Center Virtual Machine Managers, sodass ich hier einmal ein kleines PowerShell Script geschrieben habe, welches sich der VM-Speicherverwaltung im Hyper-V-Cluster widmet.
$VMs = @()
function GetCluster{
if($([string]$(Get-Service | ? Name -eq "ClusSvc").Status).ToLower() -eq "running"){
$Cluster = Get-Cluster
$Cluster = $($Cluster.Name + "." + $Cluster.Domain)
} else{
do{
$Cluster = Read-Host "Bitte den Namen des Clusters eingeben"
$Cluster = Get-Cluster -Name $Cluster -ErrorAction SilentlyContinue
if($Cluster){
$Cluster = $($Cluster.Name + "." + $Cluster.Domain)
}
}until($cluster)
}
return $Cluster
}
function GetSANPath{
# SAN Luns auflisten:
do{
$SAN = @()
$CSVS = Get-ClusterSharedVolume -Cluster $Cluster | ? State -eq "Online"
foreach ($CSV in $CSVS ) {
$CSVInfos = $CSV | select -Property Name -ExpandProperty SharedVolumeInfo
foreach ($CSVInfo in $CSVInfos ) {
$Temp_CSV = New-Object PSObject -Property @{
Name = $CSV.Name
Size = $([math]::round((($CSVInfo.Partition.Size) / 1GB), 2))
FreeSpace = $([math]::round((($CSVInfo.Partition.FreeSpace) / 1GB), 2))
UsedSpace = $([math]::round((($CSVInfo.Partition.UsedSpace) / 1GB), 2))
PercentFree = $CSVInfo.Partition.PercentFree
Path = $CSVInfo.FriendlyVolumeName
}
$SAN += $temp_csv
}
}
$CSV = $SAN | Sort-Object FreeSpace | Out-GridView -Title "Bitte das Cluster Shared Volume wählen!" -PassThru
$CSV = $CSV.Path
if(!($CSV)){
do{
$Cancel = Read-Host "Vorgang wirklich abbrechen? (j/n)"
}until($Cancel.ToLower() -eq "j" -or $Cancel.ToLower() -eq "n")
}
if($Cancel.ToLower() -eq "j"){
$CSV = "Abbruch"
}
}until($CSV)
return $CSV
}
$Cluster = GetCluster
do{
Write-Host "Bitte das Quell-ClusterSharedVolume wählen"
do{
$CSVSource = GetSANPath
}until($CSVSource)
if($CSVSource -ne "Abbruch"){
Write-Host "Bitte das Quell-ClusterSharedVolume wählen -> Ok!"
Write-Host "Alle S- und E-VMs im Cluster ($Cluster) werden ausgelesen"
$AllVMs = Get-VM -ComputerName $Cluster | where { $_.Name.ToLower().StartsWith("s") -or $_.Name.ToLower().StartsWith("e") } | Sort-Object VMName
Write-Host "Alle S- und E-VMs im Cluster ($Cluster) werden ausgelesen -> Ok!"
Write-Host "Es wird geprüft welche VMs auf dem Quell CSV ($CSVSource) liegen"
foreach($VM in $AllVMs){
$TotalFileSize = 0
foreach($VHD in $(Get-VHD -ComputerName $VM.ComputerName -VMId $VM.VMId | where { $_.Path.ToLower().StartsWith($CSVSource.ToLower()) })){
$TotalFileSize += $VHD.FileSize
$VHDPath = $VHD.Path
}
if($TotalFileSize -gt 0){
if(!($VMs.VMName -contains $VM.VMName)){
$temp_VM = New-Object PSObject -Property @{
VMName = $VM.VMName.ToUpper()
VHDPath = $($CSVSource + "\VM\" + $VM.VMName.ToUpper())
TotalSize = $([math]::round($TotalFileSize/1GB, 2))
Host = $VM.ComputerName
}
$VMs += $temp_VM
}
}
}
Write-Host "Es wird geprüft welche VMs auf dem Quell CSV ($CSVSource) liegen -> Ok!"
Write-Host "Auswahl der zu verschiebenden VM"
do{
$VMtoMove = $VMs | Select-Object VMName, TotalSize, Host, VHDPath | Sort-Object TotalSize -Descending | Out-GridView -Title "Welche VM soll verschoben werden?" -PassThru
if(!($VMtoMove)){
do{
$Cancel = Read-Host "Vorgang wirklich abbrechen? (j/n)"
}until($Cancel.ToLower() -eq "j" -or $Cancel.ToLower() -eq "n")
$VMtoMove = "Abbruch"
}
}until($VMtoMove)
if($VMtoMove -ne "Abbruch"){
Write-Host "Auswahl der zu verschiebenden VM -> Ok!"
Write-Host "Bitte das Ziel-ClusterSharedVolume wählen"
do{
$CSVDestination = GetSANPath
}until($CSVDestination)
if($CSVDestination -ne "Abbruch"){
Write-Host "Bitte das Ziel-ClusterSharedVolume wählen -> Ok!"
Write-Host "Verschiebe VM ($($VMtoMove.VMName)) von $CSVSource auf $CSVDestination"
Move-VMStorage -ComputerName $VMtoMove.Host -Name $VMtoMove.VMName -DestinationStoragePath $($CSVDestination + "\VM\" + $VMtoMove.VMName)
Write-Host "Verschiebe VM ($($VMtoMove.VMName)) von $CSVSource auf $CSVDestination -> Ok!"
$CSVSourceUNC = $("\\" + $VMtoMove.Host + "\" + $CSVSource.Replace(":","$"))
Write-Host "Bereinige $($CSVSourceUNC + "\VM\" + $VMtoMove.VMName)"
foreach($Folder in $(Get-ChildItem -Path $($CSVSourceUNC + "\VM\" + $VMtoMove.VMName) -Recurse)){
if(!(Test-Path -Path $($Folder.FullName + "\*"))){
Remove-Item -Path $Folder.FullName
} else{
Write-Host "Bitte $($Folder.FullName) prüfen!" -ForegroundColor Red
}
}
if(!(Test-Path -Path $($CSVSourceUNC + "\VM\" + $VMtoMove.VMName + "\*"))){
Remove-Item -Path $($CSVSourceUNC + "\VM\" + $VMtoMove.VMName)
Write-Host "Bereinige $($CSVSourceUNC + "\VM\" + $VMtoMove.VMName) -> Ok!"
} else{
Write-Host "Bitte $($CSVSourceUNC + "\VM\" + $VMtoMove.VMName) manuell prüfen!" -ForegroundColor Red
Start-Process -FilePath $($env:windir + "\explorer.exe") -ArgumentList $($CSVSourceUNC + "\VM\" + $VMtoMove.VMName)
Write-Host "Bereinige $($CSVSourceUNC + "\VM\" + $VMtoMove.VMName) -> Fehler!" -ForegroundColor Red
}
} else{
Write-Host "Bitte das Ziel-ClusterSharedVolume wählen -> Abbruch!" -ForegroundColor Red
Write-Host "Vorgang wurde abgebrochen!"
}
} else{
Write-Host "Auswahl der zu verschiebenden VM -> Abbruch!" -ForegroundColor Red
Write-Host "Vorgang wurde abgebrochen!"
}
} else{
Write-Host "Bitte das Quell-ClusterSharedVolume wählen -> Abbruch!" -ForegroundColor Red
Write-Host "Vorgang wurde abgebrochen!"
}
do{
$Cancel = Read-Host "Weiteres CluserSharedVolume verwalten? (j/n)"
}until($Cancel.ToLower() -eq "j" -or $Cancel.ToLower() -eq "n")
}until($Cancel.ToLower() -eq "n")
Wie so häufig, die Frage, was macht das Script denn eigentlich?
- Die Funktion „GetCluster“ prüft, ob es den Cluster-Dienst (CluSvc) auf dem aktuellen Server / PC gibt. Ist das der Fall, wird der Cluster ausgelesen. Sind wir auf einer Management-Workstation, so muss der Clusternamen eingegeben werden.
- Die Erläuterungen zur Funktion „GetSANPath“ findet sich hier.
- „GetCluster“ wird aufgerufen, um den Cluster zu ermitteln
- Die diversen Schleifen dienen als Abbruch- bzw. Neu-Ausführbedingungen. Nach dem Script ist schließlich vor dem Script! 😉
- Es wird mittels „GetSANPath“ das Quell-CSV ausgelesen.
- In meinem Fall interessieren mich nur File- und Exchange-Server. Diese Server fangen bei uns mit „S“ oder „E“ an weshalb ich alle VMs filtere, die eben nicht mit „S“ oder „E“ anfangen.
- Sobald die VMs ausgelesen sind, schaue ich mir mit „Get-VHD“ die virtuellen Harddisks der VMs auf dem Quell-CSV an und erstelle ein Objekt mit allen VMs auf dem CSV, dem Pfad zum CSV, der Größe aller VHDs sowie dem Host, wo die virtuelle Maschine derzeit läuft.
- Aus dem in „7“ erstellten Objekt wird als nächstes in einem „GridView“ die zu verschiebende VM gewählt.
- Jetzt wird das Ziel-CSV (CSVDestination) mittels der „GetSANPath“ Funktion ermittelt.
- *Trommelwirbel* Jetzt kommt der eigentliche Vorgang, der die gesamte VM auf das Ziel Cluster Shared Volume mittels „Move-VMStorage“ verschiebt. 🙂
- Leider wird nach einem erfolgreichen Verschiebevorgang nicht aufgeräumt. Dies erfolgt mit den diversen „Test-Path“ und „Remove-Item“ Commandlets. (Der Aufräumvorgang erfolgt per UNC-Pfad, da das Script bei mir auf einer Management-Workstation läuft, die keinen Zugriff auf die Cluster Shared Volumes unter „C:\ClusterSharedVolumes“ hat.)
- Sollte es beim Aufräumen und somit auch beim Verschieben einen Fehler gegeben haben, öffnet das Script die Quelle im Windows Explorer und es sollte manuell geprüft werden, was dort schiefgelaufen ist.
- Die „VM-Speicherverwaltung“ ist fertig – Yay!
Schreibe einen Kommentar