Letzte Woche hatte ich die Gelegenheit im Rahmen der PowerShell User Group Hamburg eine kleine PowerCLI Session zu machen. Ehrlich gesagt war ich etwas überracht, dass das Thema VMware PowerCLI so gut angenommen wurde. Nach meinem Empfinden liegt der Fokus doch ehr auf Windows und Azure Themen in den PowerShell User Groups.
Alle Präsentationen der PowerShell User Group Hamburg sind im GitHub Repository- Presentations zu finden.
PowerCLI Session
Meine Präsentation der PowerCLI Session ist auf Slideshare zu finden, alle Code Snippets der Demos finden sich weiter unten in diesem Artikel oder natürlich auch im GitHub Repository der PowerShell User Group Hamburg.
Code Snippets
Vielen Dank an der Stelle auch noch einmal an Luc Dekens für die Inspiration durch die VMworld 2018 Session: [VIN1992BU] – A Deep(er) Dive with PowerCLI 10 und seine Invoke-VMScriptPlus Funktion.
# Init ## Import VMware Modules Get-Module -Name VMware* -ListAvailable | Import-Module ## Connect vCenter Connect-VIServer vcenter-01.lab.local ## Prepare VMs $sourceVMName = "test" For ($i=1; $i -le 20; $i++) { $newVMName = [String]($sourceVMName + $i) New-VM -Name $newVMName -VM $sourceVMName -VMHost "esxi-01.lab.local" -Datastore "vol_vmware_01" -DiskStorageFormat Thin }
# Perf - Methoden / Get-View mit Filter ## Init Array of VMs $VMs = Get-VM ## Where-Object Cmdlet Measure-Command { $VMs | Where-Object {$_.Name -eq "test"} } ## Where Methode Measure-Command { $VMs.Where({$_.Name -eq "test"}) } ## Where Split $VMsOn, $VMsOff = $VMs.Where({$_.PowerState -eq "PoweredOn"}, "Split") $VMsOn.Count $VMsOff.Count ## Get-View mit Filter gegen Get-VM mit Where-Object Measure-Command {Get-VM -Name "test" | fl} Measure-Command {Get-View -ViewType VirtualMachine -Filter @{Name="^test"} | fl}
# PSDrive [String]$LocalPath = "C:\Users\Markus\Google Drive\PSUG HH\VMW-ESX-6.0.0-igbn-1.4.6-offline_bundle-9909123.zip" [String]$TempDatastoreName = "*local" [Array]$VMhosts = Get-VMHost $VMhosts.ForEach({ $Datastore = $_| Get-Datastore -Name $TempDatastoreName $DatastoreDriveName = "HostStore_" + $_.Name.Split(".")[0] $Datastore | New-DatastoreDrive -Name $DatastoreDriveName | Out-Null Copy-DatastoreItem -Item $LocalPath -Destination $($DatastoreDriveName + ":\") -Force:$true -Confirm:$false Remove-PSDrive -Name $DatastoreDriveName })
# ESXCLI -V2 $VMHost = $VMHosts | select -First 1 $Datastore = $VMHost | Get-Datastore -Name $TempDatastoreName $HostPath = $Datastore.ExtensionData.Info.Url.remove(0,5) + $LocalPath.Split("\")[-1] ## Install-VMhost Install-VMHostPatch -VMHost $VMHost -HostPath $HostPath ## ESXCLI V2 $esxcli2 = Get-ESXCLI -VMHost $VMhost -V2 ### List $esxcli2.software.vib.list.Invoke() | FT -AutoSize ### Install $CreateArgs = $esxcli2.software.vib.install.CreateArgs() $CreateArgs.depot = $HostPath $InstallResponse = $esxcli2.software.vib.install.Invoke($CreateArgs)
# Invoke-VMScript ## Function and Class from Luc (With Modification) class MyOBN:System.Management.Automation.ArgumentTransformationAttribute { [ValidateSet( 'Cluster', 'Datacenter', 'Datastore', 'DatastoreCluster', 'Folder', 'VirtualMachine', 'VirtualSwitch', 'VMHost', 'VIServer' )] [String]$Type MyOBN([string]$Type) { $this.Type = $Type } [object] Transform([System.Management.Automation.EngineIntrinsics]$engineIntrinsics, [object]$inputData) { if ($inputData -is [string]) { if (-NOT [string]::IsNullOrWhiteSpace( $inputData )) { $cmdParam = "-$(if($this.Type -eq 'VIServer'){'Server'}else{'Name'}) $($inputData)" $sCmd = @{ Command = "Get-$($this.Type.Replace('VirtualMachine','VM')) $($cmdParam)" } return (Invoke-Expression @sCmd) } } elseif ($inputData.GetType().Name -match "$($this.Type)Impl") { return $inputData } elseif ($inputData.GetType().Name -eq 'Object[]') { return ($inputData | ForEach-Object { if ($_ -is [String]) { return (Invoke-Expression -Command "Get-$($this.Type.Replace('VirtualMachine','VM')) -Name `$_") } elseif ($_.GetType().Name -match "$($this.Type)Impl") { $_ } }) } throw [System.IO.FileNotFoundException]::New() } } function Invoke-VMScriptPlus { <# .SYNOPSIS Runs a script in a Linux guest OS. The script can use the SheBang to indicate which interpreter to use. .DESCRIPTION This function will launch a script in a Linux guest OS. The script supports the SheBang line for a limited set of interpreters. .NOTES Author: Luc Dekens Version: 1.0 14/09/17 Initial release 1.1 14/10/17 Support bash here-document 2.0 01/08/18 Support Windows guest OS, bat & powershell 2.1 03/08/18 PowerShell she-bang for Linux 2.2 17/08/18 Added ScriptEnvironment .PARAMETER VM Specifies the virtual machines on whose guest operating systems you want to run the script. .PARAMETER GuestUser Specifies the user name you want to use for authenticating with the virtual machine guest OS. .PARAMETER GuestPassword Specifies the password you want to use for authenticating with the virtual machine guest OS. .PARAMETER GuestCredential Specifies a PSCredential object containing the credentials you want to use for authenticating with the virtual machine guest OS. .PARAMETER ScriptText Provides the text of the script you want to run. You can also pass to this parameter a string variable containing the path to the script. Note that the function will add a SheBang line, based on the ScriptType, if none is provided in the script text. .PARAMETER ScriptType The supported Linux interpreters. Currently these are bash,perl,python3,nodejs,php,lua .PARAMETER ScriptEnvironment A string array with environment variables. These environment variables are available to the script from ScriptText .PARAMETER GuestOSType Indicates which type of guest OS the VM is using. The parameter accepts Windows or Linux. This parameter is a fallback for when the function cannot determine which OS Family the Guest OS belongs to .PARAMETER PSv6Version Indicates which PowerShell Core version to use. The default is 6.0.2 .PARAMETER CRLF Switch to indicate of the NL that is returned by Linux, shall be converted to a CRLF .PARAMETER Server Specifies the vCenter Server systems on which you want to run the cmdlet. If no value is passed to this parameter, the command runs on the default servers. For more information about default servers, see the description of Connect-VIServer. .EXAMPLE $pScript = @' #!/usr/bin/env perl use strict; use warnings; print "Hello world\n"; '@ $sCode = @{ VM = $VM GuestCredential = $cred ScriptType = 'perl' ScriptText = $pScript } Invoke-VMScriptPlus @sCode .EXAMPLE $pScript = @' print("Happy 10th Birthday PowerCLI!") '@ $sCode = @{ VM = $VM GuestCredential = $cred ScriptType = 'python3' ScriptText = $pScript } Invoke-VMScriptPlus @sCode #> [cmdletbinding()] param( [parameter(Mandatory = $true, ValueFromPipeline = $true)] [MyOBN('VirtualMachine')] [VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine[]]$VM, [Parameter(Mandatory = $true, ParameterSetName = 'PlainText')] [String]$GuestUser, [Parameter(Mandatory = $true, ParameterSetName = 'PlainText')] [SecureString]$GuestPassword, [Parameter(Mandatory = $true, ParameterSetName = 'PSCredential')] [PSCredential[]]$GuestCredential, [Parameter(Mandatory = $true)] [String]$ScriptText, [Parameter(Mandatory = $true)] [ValidateSet('bash', 'perl', 'python3', 'nodejs', 'php', 'lua', 'powershell', 'powershellv6', 'bat')] [String]$ScriptType, [String[]]$ScriptEnvironment, [ValidateSet('Windows', 'Linux')] [String]$GuestOSType, [String]$PSv6Version = '6.0.2', [Switch]$CRLF, [MyOBN('VIServer')] [VMware.VimAutomation.ViCore.Types.V1.VIServer]$Server = $global:DefaultVIServer ) Begin { $si = Get-View ServiceInstance $guestMgr = Get-View -Id $si.Content.GuestOperationsManager $gFileMgr = Get-View -Id $guestMgr.FileManager $gProcMgr = Get-View -Id $guestMgr.ProcessManager $shebangTab = @{ 'bash' = '#!/usr/bin/env bash' 'perl' = '#!/usr/bin/env perl' 'python3' = '#!/usr/bin/env python3' 'nodejs' = '#!/usr/bin/env nodejs' 'php' = '#!/usr/bin/env php' 'lua' = '#!/usr/bin/env lua' 'powershellv6' = '#!/usr/bin/env pwsh' } } Process { foreach ($vmInstance in $VM) { # Preamble if ($vmInstance.PowerState -ne 'PoweredOn') { Write-Error "VM $($vmInstance.Name) is not powered on" continue } if ($vmInstance.ExtensionData.Guest.ToolsRunningStatus -ne 'guestToolsRunning') { Write-Error "VMware Tools are not running on VM $($vmInstance.Name)" continue } $moref = $vmInstance.ExtensionData.MoRef # Are we targetting a Windows or a Linux box? if (-not $GuestOSType) { switch -Regex ($vmInstance.Guest.OSFullName) { 'Windows' { $GuestOSType = 'Windows' if ('bat', 'powershell', 'powershellv6' -notcontains $ScriptType) { Write-Error "For a Windows guest OS the ScriptType can be Bat, PowerShell or PowerShellv6" continue } } 'Linux' { $GuestOSType = 'Linux' if ('bat', 'powershell' -contains $ScriptType) { Write-Error "For a Linux guest OS the ScriptType cannot be Bat or PowerShell" continue } } Default { Write-Error "Unable to determine the guest OS type on VM $($vmInstance.Name)" continue } } } if ($GuestOSType -eq 'Linux') { Write-Verbose "Seems to be a Linux guest OS" # Test if code contains a SheBang, otherwise add it $targetCode = $shebangTab[$ScriptType] if ($ScriptText -notmatch "^$($targetCode)") { Write-Verbose "Add SheBang $targetCode" $ScriptText = "$($targetCode)`n`r$($ScriptText)" } } # Create Authentication Object (User + Password) if ($PSCmdlet.ParameterSetName -eq 'PSCredential') { $GuestUser = $GuestCredential.GetNetworkCredential().username $plainGuestPassword = $GuestCredential.GetNetworkCredential().password } if ($PSCmdlet.ParameterSetName -eq 'PlainText') { $bStr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($GuestPassword) $plainGuestPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bStr) } $auth = New-Object VMware.Vim.NamePasswordAuthentication $auth.InteractiveSession = $false $auth.Username = $GuestUser $auth.Password = $plainGuestPassword # Copy script to temp file in guest # Create temp file for script $suffix = '' if ($ScriptType -eq 'bat') { $suffix = ".cmd" } if ('powershell', 'powershellv6' -contains $ScriptType) { $suffix = ".ps1" } Try { $tempFile = $gFileMgr.CreateTemporaryFileInGuest($moref, $auth, "$($env:USERNAME)_$($PID)", $suffix, $null) Write-Verbose "Created temp script file in guest OS $($tempFile.Name)" } Catch { Throw "$error[0].Exception.Message" } # Create temp file for output Try { $tempOutput = $gFileMgr.CreateTemporaryFileInGuest($moref, $auth, "$($env:USERNAME)_$($PID)_output", $null, $null) Write-Verbose "Created temp output file in guest OS $($tempOutput.Name)" } Catch { Throw "$error[0].Exception.Message" } # Copy script to temp file if ($GuestOSType -eq 'Linux') { $ScriptText = $ScriptText.Split("`r") -join '' } $attr = New-Object VMware.Vim.GuestFileAttributes $clobber = $true $filePath = $gFileMgr.InitiateFileTransferToGuest($moref, $auth, $tempFile, $attr, $ScriptText.Length, $clobber) $ip = $filePath.split('/')[2].Split(':')[0] ## Modiefed by Markus Kraus: $hostName = (Resolve-DnsName -Name $ip).where({$_.Section -eq "Answer"}) | Select-Object -ExpandProperty NameHost $filePath = $filePath.replace($ip, $hostName) $copyResult = Invoke-WebRequest -Uri $filePath -Method Put -Body $ScriptText if ($copyResult.StatusCode -ne 200) { Throw "ScripText copy failed!`rStatus $($copyResult.StatusCode)`r$(($copyResult.Content | ForEach-Object{[char]$_}) -join '')" } Write-Verbose "Copied scipttext to temp script file" # Get current environment variables $SystemEnvironment = $gProcMgr.ReadEnvironmentVariableInGuest($moref, $auth, $null) # Run script switch ($GuestOSType) { 'Linux' { # Make temp file executable $spec = New-Object VMware.Vim.GuestProgramSpec $spec.Arguments = "751 $tempFile" $spec.ProgramPath = '/bin/chmod' Try { $procId = $gProcMgr.StartProgramInGuest($moref, $auth, $spec) Write-Verbose "Run script file" } Catch { Throw "$error[0].Exception.Message" } # Run temp file $spec = New-Object VMware.Vim.GuestProgramSpec if($ScriptEnvironment){ $spec.EnvVariables = $SystemEnvironment + $ScriptEnvironment } $spec.Arguments = " > $($tempOutput)" $spec.ProgramPath = "$($tempFile)" Try { $procId = $gProcMgr.StartProgramInGuest($moref, $auth, $spec) Write-Verbose "Run script with '$($tempFile) > $($tempOutput)'" } Catch { Throw "$error[0].Exception.Message" } } 'Windows' { # Run temp file $spec = New-Object VMware.Vim.GuestProgramSpec if($ScriptEnvironment){ $spec.EnvVariables = $SystemEnvironment + $ScriptEnvironment } switch ($ScriptType) { 'PowerShell' { $spec.Arguments = " /C powershell -NonInteractive -File $($tempFile) > $($tempOutput)" $spec.ProgramPath = "cmd.exe" } 'PowerShellv6' { $spec.Arguments = " /C ""C:\Program Files\PowerShell\$($PSv6Version)\pwsh.exe"" -NonInteractive -File $($tempFile) > $($tempOutput)" $spec.ProgramPath = "cmd.exe" } 'Bat' { $spec.Arguments = " /s /c cmd > $($tempOutput) 2>&1 /s /c $($tempFile)" $spec.ProgramPath = "cmd.exe" } } Try { $procId = $gProcMgr.StartProgramInGuest($moref, $auth, $spec) Write-Verbose "Run script with '$($spec.ProgramPath) $($spec.Arguments)'" } Catch { Throw "$error[0].Exception.Message" } } } # Wait for script to finish Try { $pInfo = $gProcMgr.ListProcessesInGuest($moref, $auth, @($procId)) Write-Verbose "Wait for process to end" while ($pInfo -and $null -eq $pInfo.EndTime) { Start-Sleep 1 $pInfo = $gProcMgr.ListProcessesInGuest($moref, $auth, @($procId)) } } Catch { Throw "$error[0].Exception.Message" } # Retrieve output from script $fileInfo = $gFileMgr.InitiateFileTransferFromGuest($moref, $auth, $tempOutput) $fileContent = Invoke-WebRequest -Uri $fileInfo.Url -Method Get if ($fileContent.StatusCode -ne 200) { Throw "Retrieve of script output failed!`rStatus $($fileContent.Status)`r$(($fileContent.Content | ForEach-Object{[char]$_}) -join '')" } Write-Verbose "Get output from $($fileInfo.Url)" # Clean up # Remove output file $gFileMgr.DeleteFileInGuest($moref, $auth, $tempOutput) Write-Verbose "Removed file $($tempOutput.Name)" # Remove temp script file $gFileMgr.DeleteFileInGuest($moref, $auth, $tempFile) Write-Verbose "Removed file $($tempFile.Name)" New-Object PSObject -Property @{ VM = $vmInstance ScriptOutput = & { $out = ($fileContent.Content | ForEach-Object {[char]$_}) -join '' if ($CRLF) { $out.Replace("`n", "`n`r") } else { $out } } Pid = $procId PidOwner = $pInfo.Owner Start = $pInfo.StartTime Finish = $pInfo.EndTime ExitCode = $pInfo.ExitCode ScriptType = $ScriptType ScriptSize = $ScriptText.Length ScriptText = $ScriptText GuestOS = $GuestOSType } } } } ## Example $code =@' if [ -f /etc/os-release ] then cat /etc/os-release else echo 'File os-release not found' fi '@ $Pass = ConvertTo-SecureString '<Password>' -AsPlainText -Force $sInvP = @{ VM = 'PhotonOS' ScriptType = 'Bash' ScriptText = $code GuestOSType = 'Linux' GuestUser = 'root' GuestPassword = $Pass } Invoke-VMScriptPlus @sInvP
# VM UUID "DeepDive" function Get-VMID { [CmdletBinding()] param( [Parameter(Mandatory=$true, ValueFromPipeline=$True, Position=0)] [VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $myVMs ) Process { $MyView = @() ForEach ($myVM in $myVMs){ $UUIDReport = [PSCustomObject] @{ Name = $myVM.name UUID = $myVM.extensiondata.Config.UUID InstanceUUID = $myVM.extensiondata.config.InstanceUUID LocationID = $myVM.extensiondata.config.LocationId MoRef = $myVM.extensiondata.Moref.Value } $MyView += $UUIDReport } $MyView } } function Convert-UUID { ## http://blog-stack.net/convert-windows-uuid-to-vmware-vmx-uuid-format/ Param ( [Parameter(Mandatory=$True)] [ValidateNotNullorEmpty()] [String]$UUIDwin ) $0,$1,$2,$3,$4 = $UUIDwin.Split("-") [Array]$UUIDBlocks = [String]$UUIDwin.Split("-") [Array]$Block0Split = $0 -split '(..)' | Where-Object { $_ } [array]::Reverse($Block0Split) $Block0 = $Block0Split -join('') [Array]$Block1Split = $1 -split '(..)' | Where-Object { $_ } [array]::Reverse($Block1Split) $Block1 = $Block1Split -join('') [Array]$Block2Split = $2 -split '(..)' | Where-Object { $_ } [array]::Reverse($Block2Split) $Block2 = $Block2Split -join('') [String]$UUID = ($Block0 + "-" + $Block1 + "-" + $Block2 + "-" + $3 + "-" + $4).ToLower() return $UUID } ## Match OS and VMware UUID ### Get BIOS UUID from Wiondows OS $code =@' (Get-WmiObject Win32_ComputerSystemProduct).UUID '@ $user = 'local\administrator' $pswd = '<Password>' $sPswd = ConvertTo-SecureString -String $pswd -AsPlainText -Force $cred = New-Object System.Management.Automation.PSCredential -ArgumentList $user,$sPswd $sInvP = @{ VM = 'Veeam-03' ScriptType = 'PowerShell' GuestOSType = 'Windows' ScriptText = $code GuestCredential = $cred } ### Prepare Output Remove-Variable UUIDwin, UUIDreturn -ErrorAction SilentlyContinue $UUIDreturn = Invoke-VMScriptPlus @sInvP [String]$UUIDwin = $UUIDreturn.ScriptOutput ### Output Convert-UUID -UUIDwin $UUIDwin Get-VM -Name "Veeam-03" | Get-VMID | select UUID