My InvokeVMScriptPlus function serves me well while interacting with the guest OS on a VM. And I’m apparently not the only one that uses the function. This post introduces Invoke-VMScriptPlus v3.
The original Invoke-VMScriptPlus post, and the addition of PS Core support, described in the Invoke-VMScriptPlus v2 post, keep being some of my most read posts. Time for another update.

In this v3 version, I introduce some new features to the function.
- PSv6 and PSv7 support
- Use files (input and output) from within your scripts
- improved sudo support
Update July 2nd 2021
- Fixed incorrect variable NameHost
- Added tests to detect IP or FQDN in URI
- Updated test to check if type TrustAllCertsPolicy exists or not
Update April 15th 2020
- Added SkipCertificateCheck switch
Update January 16th 2020
- Bug fix which occured when connected to an ESXi node
Update November 18th 2019
- Added NoIPinCert switch
The Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 |
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 2.3 11/03/19 Resolve IP to FQDN to support certificate for ESXi node 2.4 22/04/19 Switch to provide password inline to 'sudo' lines 2.5 07/06/19 Switch WaitForToolsVersionChange to wait for a version change 3.0 17/11/19 Added powershellv7 support, added InFile & OutFile 3.1 18/11/19 Added switch NoIPinCert 3.2 15/04/20 Added switch SkipCertificateCheck 3.3 02/07/21 Fixed issue with NameHost, added test for IP or FQDN, changed type test TrustAllCertsPolicy .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,powershell,powershellv6,powershellv7 .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 CRLF Switch to indicate of the NL that is returned by Linux, shall be converted to a CRLF .PARAMETER Sudo Switch to convert all 'sudo' lines to an inline password 'sudo' line. Only taken into account when the GuestOSType is 'Linux' .PARAMETER KeepFiles Switch to indicate that the temporary files, the script and the output files, shall not be deleted. Only to be used for debugging purposes. .PARAMETER InFile One or more files that will be copied to the guest OS. These files will be copied to the directory from where the script will run and can be used from within the script. .PARAMETER InFile One or more files that will be copied from the guest OS after the script has ran. These files will be copied from the directory from where the script runs. .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. .PARAMETER WaitForToolsVersionChange When the invoked code changes the version of the VMware Tools, this switch tells the function to wait till this version change is visible in the script .PARAMETER NoIPinCert When certificates are used that do not contain the IP address of the ESXi node as a Subject Alternative Name (SAN), this switch tells the function to convert the IP address in all URI used for file transfers, to a FQDN. .PARAMETER NoIPinCert When certificates are used that do not contain the IP address of the ESXi node as a Subject Alternative Name (SAN), this switch tells the function to convert the IP address in all URI used for file transfers, to a FQDN. .PARAMETER SkipCertificateCheck When a non-trusted certificate is used on the ESXi node that hosts the targetted VM, the transfer of files to and from the VM's Guest OS will fail. This switch tells the function to ignore invalid certificates on the ESXi node. .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 .EXAMPLE $pScript = @' Get-Content -Path .\MyInput.txt | Set-Content -Path .\MyOutput.txt '@ $sCode = @{ VM = $VM GuestCredential = $cred ScriptType = 'powershellv7' ScriptText = $pScript InFile = 'C:\Test\MyInput.txt' OutFile = 'C:\Report\MyOutput.txt' } Invoke-VMScriptPlus @sCode #> [cmdletbinding()] param( [parameter(Mandatory = $true, ValueFromPipeline = $true)] [MyOBN('VirtualMachine')] [VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine[]]$VM, [Parameter(Mandatory = $true, ParameterSetName = 'TextScript')] [Parameter(Mandatory = $true, ParameterSetName = 'TextExe')] [String]$GuestUser, [Parameter(Mandatory = $true, ParameterSetName = 'TextScript')] [Parameter(Mandatory = $true, ParameterSetName = 'TextExe')] [SecureString]$GuestPassword, [Parameter(Mandatory = $true, ParameterSetName = 'CredScript')] [Parameter(Mandatory = $true, ParameterSetName = 'CredExe')] [PSCredential[]]$GuestCredential, [Parameter(Mandatory = $true, ParameterSetName = 'TextScript')] [Parameter(Mandatory = $true, ParameterSetName = 'CredScript')] [String]$ScriptText, [Parameter(Mandatory = $true, ParameterSetName = 'TextScript')] [Parameter(Mandatory = $true, ParameterSetName = 'CredScript')] [ValidateSet('bash', 'perl', 'python3', 'nodejs', 'php', 'lua', 'powershell', 'powershellv6', 'powershellv7', 'bat', 'exe')] [String]$ScriptType, [Parameter(Mandatory = $true, ParameterSetName = 'TextExe')] [Parameter(Mandatory = $true, ParameterSetName = 'CredExe')] [string]$ExeName, [String[]]$ScriptEnvironment, [ValidateSet('Windows', 'Linux')] [String]$GuestOSType, [Switch]$CRLF, [Switch]$Sudo, [Switch]$KeepFiles, [MyOBN('VIServer')] [VMware.VimAutomation.ViCore.Types.V1.VIServer]$Server = $global:DefaultVIServer, [Switch]$WaitForToolsVersionChange, [String[]]$InFile, [String[]]$OutFile, [Switch]$NoIPinCert, [Switch]$SkipCertificateCheck ) Begin { #region Helper functions function Send-GuestFile { [cmdletbinding()] param( [Parameter(Mandatory = $true)] [String]$File, [Parameter(Mandatory = $true, ParameterSetName = 'File')] [String]$Source, [Parameter(Mandatory = $true, ParameterSetName = 'Data')] [String]$Data ) if ($PSCmdlet.ParameterSetName -eq 'File') { $Data = Get-Content -Path $Source -Raw } $attr = New-Object VMware.Vim.GuestFileAttributes $clobber = $true $fileInfo = $gFileMgr.InitiateFileTransferToGuest($moref, $auth, $File, $attr, $Data.Length, $clobber) if ($Server.ProductLine -eq 'embeddedEsx') { $fileInfo = $fileInfo.Replace('*', ([System.Uri]$server.ServiceUri).Host) } if ($NoIPinCert.IsPresent) { $ip = $fileInfo.split('/')[2].Split(':')[0] if ($ip -as [IPAddress]) { $hostName = Resolve-DnsName -Name $ip | Select-Object -ExpandProperty NameHost $fileInfo = $fileInfo.replace($ip, $hostName) } } $sWeb = @{ Uri = $fileInfo Method = 'Put' Body = $Data } if ($SkipCertificateCheck.IsPresent) { if ($PSVersionTable.PSVersion.Major -lt 6) { [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy } else { $sWeb.Add('SkipCertificateCheck', $true) } } Write-Verbose -Message "Copying $($PSCmdlet.ParameterSetName) to $File" $copyResult = Invoke-WebRequest @sWeb if ($copyResult.StatusCode -ne 200) { Throw "ScripText copy failed!`rStatus $($copyResult.StatusCode)`r$(($copyResult.Content | ForEach-Object{[char]$_}) -join '')" } } function Receive-GuestFile { [cmdletbinding()] param( [String]$Source, [String]$File ) $fileInfo = $gFileMgr.InitiateFileTransferFromGuest($moref, $auth, $Source) if ($Server.ProductLine -eq 'embeddedEsx') { $fileInfo.Url = $fileInfo.Url.Replace('*', ([System.Uri]$server.ServiceUri).Host) } if ($NoIPinCert.IsPresent) { $ip = $fileInfo.Url.split('/')[2].Split(':')[0] if ($ip -as [IPAddress]) { hostName = Resolve-DnsName -Name $ip | Select-Object -ExpandProperty NameHost $fileInfo.Url = $fileInfo.Url.replace($ip, $hostName) } } $sWeb = @{ Uri = $fileInfo.Url Method = 'Get' } if ($SkipCertificateCheck.IsPresent) { if ($PSVersionTable.PSVersion.Major -lt 6) { [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy } else { $sWeb.Add('SkipCertificateCheck', $true) } } $fileContent = Invoke-WebRequest @sWeb if ($fileContent.StatusCode -ne 200) { Throw "Retrieve of script output failed!`rStatus $($fileContent.Status)`r$(($fileContent.Content | ForEach-Object{[char]$_}) -join '')" } if ($File) { $fileContent.Content | Set-Content -Path $File -Encoding byte -Confirm:$false } else { $fileContent.Content } } #endregion #region Set up guest operations $si = Get-View ServiceInstance -Server $Server $guestMgr = Get-View -Id $si.Content.GuestOperationsManager $gFileMgr = Get-View -Id $guestMgr.FileManager $gProcMgr = Get-View -Id $guestMgr.ProcessManager #endregion #region Set up shebang table $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' 'powershellv7' = '#!/usr/bin/env pwsh-preview' } #endregion #region Handle SkipCertificateCheck (if used) if ($SkipCertificateCheck.IsPresent -and $PSVersionTable.PSVersion.Major -lt 6 -and -not ('TrustAllCertsPolicy' -as [Type])) { Add-Type @' using System.Net; using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { public bool CheckValidationResult( ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; } } '@ } #endregion } Process { foreach ($vmInstance in $VM) { #region Test conditions for running script in guest OS if ($vmInstance.PowerState -ne 'PoweredOn') { Write-Error "VM $($vmInstance.Name) is not powered on" continue } $vmInstance.ExtensionData.UpdateViewData('Guest') if ($vmInstance.ExtensionData.Guest.ToolsRunningStatus -ne 'guestToolsRunning') { Write-Error "VMware Tools are not running on VM $($vmInstance.Name)" continue } if (-not $vmInstance.ExtensionData.Guest.GuestOperationsReady) { Write-Error "VM $($vmInstance.Name) is not ready to use Guest Operations" continue } $moref = $vmInstance.ExtensionData.MoRef #endregion #region Create Authentication Object (User + Password) if ('CredScript', 'CredExe' -contains $PSCmdlet.ParameterSetName) { $GuestUser = $GuestCredential.GetNetworkCredential().username $plainGuestPassword = $GuestCredential.GetNetworkCredential().password } if ('TextScript', 'TextExe' -contains $PSCmdlet.ParameterSetName) { $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 #endregion #region Determine GuestOSType if (-not $GuestOSType) { Write-Verbose "No GuestOSType value provided. Trying to determine now." switch -Regex ($vmInstance.Guest.OSFullName) { 'Windows' { Write-Verbose "It's a Windows guest OS" $GuestOSType = 'Windows' if (-not $ExeName -and 'bat', 'powershell', 'powershellv6', 'powershellv7' -notcontains $ScriptType) { Write-Verbose "Invalid scripttype provided" Write-Error "For a Windows guest OS the ScriptType can be Bat, PowerShell, PowerShellv6 or PowerShellv7" continue } } 'Linux' { Write-Verbose "It's a Linux guest OS" $GuestOSType = 'Linux' if (-not $ExeName -and 'bat', 'powershell' -contains $ScriptType) { Write-Verbose "Invalid scripttype provided" Write-Error "For a Linux guest OS the ScriptType cannot be Bat" continue } } Default { Write-Verbose "Can't determine guest OS type." Write-Error "Unable to determine the guest OS type on VM $($vmInstance.Name)" Write-Error "Try using the GuestOSType parameter." 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)" } # Take care of the 'sudo' switch if ($Sudo) { Write-Verbose "Setting up sudo usage" $ScriptText = ($ScriptText | ForEach-Object -Process { $_ -replace 'sudo', "echo $plainGuestPassword | sudo -S" }) } } #endregion #region Create a temp directory $tempFolder = $gFileMgr.CreateTemporaryDirectoryInGuest($moref, $auth, "$($env:USERNAME)_$($PID)", $null, $null) Write-Verbose "Created temp folder in guest OS $tempFolder" #endregion #region Create temp file for script $suffix = '' if ('bat', 'exe' -contains $ScriptType) { $suffix = ".cmd" } if ('powershell', 'powershellv6', 'powershellv7' -contains $ScriptType) { $suffix = ".ps1" } Try { $tempFile = $gFileMgr.CreateTemporaryFileInGuest($moref, $auth, "$($env:USERNAME)_$($PID)", $suffix, $tempFolder) Write-Verbose "Created temp script file in guest OS $tempFile" } Catch { Write-Verbose "Encountered a problem creating the script file in the guest OS" Throw "$error[0].Exception.Message" } #endregion #region Create temp file for output Try { $tempOutput = $gFileMgr.CreateTemporaryFileInGuest($moref, $auth, "$($env:USERNAME)_$($PID)_output", $null, $tempFolder) Write-Verbose "Created temp output file in guest OS $tempOutput" } Catch { Write-Verbose "Encountered a problem creating the output file in the guest OS" Throw "$error[0].Exception.Message" } #endregion #region Copy script to temp file if ($ExeName) { Send-GuestFile -Data $ExeName -File $tempFile Write-Verbose "Copied ExeName to temp script file" } else { if ($GuestOSType -eq 'Linux') { $ScriptText = $ScriptText.Split("`r") -join '' } Send-GuestFile -Data $ScriptText -File $tempFile Write-Verbose "Copied scripttext to temp script file" } #endregion #region Get current environment variables $SystemEnvironment = $gProcMgr.ReadEnvironmentVariableInGuest($moref, $auth, $null) #endregion #region Copy InFiles to to guest OS if ($InFile) { $InFile | ForEach-Object -Process { $destinationFilePath = "$tempFolder/$(Split-Path -Path $_ -Leaf)" Write-Verbose "Upload InFile $_" Send-GuestFile -Source $_ -File $destinationFilePath } } #endregion #region Run script if ($WaitForToolsVersionChange) { $toolsVersion = $vmInstance.ExtensionData.Guest.ToolsVersion } 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 "Make script file executable" } Catch { Write-Verbose "Encountered a problem making the script file executable in the guest OS" Throw "$error[0].Exception.Message" } # Run temp file $spec = New-Object VMware.Vim.GuestProgramSpec if ($ScriptEnvironment) { $spec.EnvVariables = $SystemEnvironment + $ScriptEnvironment } $spec.Arguments = " > $($tempOutput) 2>&1" $spec.ProgramPath = "$($tempFile)" $spec.WorkingDirectory = $tempFolder Try { $procId = $gProcMgr.StartProgramInGuest($moref, $auth, $spec) Write-Verbose "Run script with '$($tempFile) > $($tempOutput)'" } Catch { Write-Verbose "Encountered a problem running the script file in the guest OS" Throw "$error[0].Exception.Message" } } 'Windows' { # Run temp file $spec = New-Object VMware.Vim.GuestProgramSpec $spec.WorkingDirectory = $tempFolder if ($ScriptEnvironment) { $spec.EnvVariables = $SystemEnvironment + $ScriptEnvironment } if ($ExeName) { $spec.ProgramPath = "cmd.exe" $spec.Arguments = " /s /c start """" ""$ExeName""" } else { switch ($ScriptType) { 'PowerShell' { $spec.Arguments = " /C powershell -NonInteractive -File $($tempFile) > $($tempOutput)" $spec.ProgramPath = "cmd.exe" } { 'PowerShellv6', 'PowerShellv7' -contains $_ } { $psCmd = 'pwsh.exe' if ($ScriptType -eq 'PowerShellv7') { $psCmd = 'pwsh-preview.exe' } $spec.Arguments = " /C ""$psCmd"" -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 { Write-Verbose "Encountered a problem running the script file in the guest OS" Throw "$error[0].Exception.Message" } } } if ($WaitForToolsVersionChange) { Write-Verbose "Waiting for VMware Tools version to change" while ($toolsVersion -eq $vmInstance.ExtensionData.Guest.ToolsVersion) { Start-Sleep -Seconds 1 $vmInstance.ExtensionData.UpdateViewData('Guest') } Write-Verbose "VMware Tools version changed from $toolsVersion to $($vmInstance.ExtensionData.Guest.ToolsVersion)" } #endregion #region 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 { Write-Verbose "Encountered a problem waiting for the script to end in the guest OS" Throw "$error[0].Exception.Message" } #endregion #region Retrieve output from script Write-Verbose "Get output from $tempOutput" $scriptOutput = Receive-GuestFile -Source $tempOutput #endregion #region Copy OutFiles from guest OS if ($OutFile) { $OutFile | ForEach-Object -Process { $sourceFilePath = "$tempFolder/$_" Write-Verbose "Download OutFile $_" Receive-GuestFile -Source $sourceFilePath -File $_ } } #endregion #region Clean up # Remove temporary folder if (-not $KeepFiles) { $gFileMgr.DeleteDirectoryInGuest($moref, $auth, $tempFolder, $true) Write-Verbose "Removed folder $tempFolder" } #endregion #region Package result in object New-Object PSObject -Property @{ VM = $vmInstance ScriptOutput = & { $out = ($scriptOutput | 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 OutFiles = $OutFile GuestOS = $GuestOSType } #endregion } } } |
Annotations
The following annotations will only document the additions to the v2 version of the function. Refer to the blog posts mentioned in the introduction to see the annotations of the previous versions of the function.
Line 191-192: Addition/change of the script types. Now includes powershellv6 and powershellv7.
Line 201: A switch that allows the caller to ask for sudo support. In practice the function will pipe, via an echo command, the guest credential’s password to each sudo command in the script.
Line 206-207: New parameters InFile and OutFile. They permit files to be copied to/from the script environment.
Line 208: The NoIPforCert switch
Line 209: The SkipCertificateCheck switch
Line 214-262: Internal helper function to send files from the caller’s environment to the guest OS.
Line 248-255,286-293: The SkipCertificateCheck switch calls Invoke-WebRequest with the option to not check the certificate. In pre-V6 that is done through the CertificatePolicy class, in PS v6 and higher, the SkipCertificateCheck switch on Invoke-WebRequest is used.
Line 264-307: Internal helper function to receive files from the guest OS into the caller’s environment.
Line 237-242,276-281: If the NoIPforCert switch is $true, the IP address in the URI returned by the ESXi node (where the VM is running) will be converted to the FQDN of the ESXi node.
Line 248-255,286-293: If the SkipCertificateCheck switch is used, the following Invoke-WebRequest is called with the setting to check checking certificates.
Line 325-326: New she-bang entries for PowerShell v6 and v7. Note that at the time this post was published, that v7 was still in preview.
Line 331-345: When the SkipCertificateCheck is used and the PS version is pre-V6 and the type hasn’t been declared yet, the TrustAllCertsPolicy class is defined.
Line 365-369: Additional ‘ready’ test. If this property is not $true, the VMware Tools inside the guest OS are not ready to accept any GuestOperations related calls.
Line 400: For any Windows guest OS, the function currently only accepts BAT, PS, PSv6 and PSv7 scripts.
Line 411: For any Linux (this includes MacOS) guest OS, the function accepts all scripttypes, except BAT and PowerShell (meaning PS pre-v6).
Line 438-444: sudo support. Each line of the user’s script containg the sudo command, will be prefixed with an echo command, which will provide the password to the sudo prompt.
Line 465: In this version of the function, all files will be stored in a temporary directory in the guest OS.
Line 510-517: The function allows the caller to copy one or more files, from the caller’s environment to the temporary folder in the guest OS. Since these files will be in the same folder as the actual script, the script can reference these files.
Line 581-595: The function supports for a Windows guest OS, the use of PSv5.*, PSV6 and PSv7.
Line 651-658: The function allows the caller to copy one or more files, from the folder, where the script ran in the guest OS, to the caller’s environment. This allows an alternative method to send data back to caller besides the stdin channel.
Line 663-667: When the KeepFiles switch is not used, the function will remove the temporary directory in the guest OS, where all the script related files are stored.
Usage
The following section will show some examples of how to use the new features introduced in the v3 of the Invoke-VMScriptPlus function.
Sudo
One of security measure on many Linux systems is that you need to use sudo to run commands with elevated privileges.
This is a typical example of such a case.
1 2 3 4 5 6 7 8 9 10 11 12 |
$vmName = 'ubuntuVM' $credVM = Get-VICredentialStoreItem -Host $vmName $cred = New-Object System.Management.Automation.PSCredential ($credVM.User, (ConvertTo-SecureString $credVM.Password -AsPlainText -Force)) $sInvoke = @{ VM = $vmName GuestCredential = $cred ScriptText = 'apt install gawk' ScriptType = 'bash' } Invoke-VMScriptPlus @sInvoke |
The OS returns the following error.

Ok, let’s place sudo in front of that.
1 2 3 4 5 6 7 8 9 10 11 12 |
$vmName = 'ubuntuVM' $credVM = Get-VICredentialStoreItem -Host $vmName $cred = New-Object System.Management.Automation.PSCredential ($credVM.User, (ConvertTo-SecureString $credVM.Password -AsPlainText -Force)) $sInvoke = @{ VM = $vmName GuestCredential = $cred ScriptText = 'sudo apt install gawk' ScriptType = 'bash' } Invoke-VMScriptPlus @sInvoke |
But that just makes the next issue obvious. How to answer to a prompt in a script. The method to solve this quite simple. Echo the password and then pipe it to the sudo command.

But this not always a practical solution, and probably not very safe either. To avoid having to handle this in the code you sent to the guest OS, the Invoke-VMScriptPlus function has the Sudo switch.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$vmName = 'ubuntuVM' $credVM = Get-VICredentialStoreItem -Host $vmName $cred = New-Object System.Management.Automation.PSCredential ($credVM.User, (ConvertTo-SecureString $credVM.Password -AsPlainText -Force)) $sInvoke = @{ VM = $vmName GuestCredential = $cred ScriptText = 'sudo apt install gawk' ScriptType = 'bash' Sudo = $true } Invoke-VMScriptPlus @sInvoke |
It will extract the password from the GuestCredential parameter value, and insert the echo with the password on each line in your code that starts with sudo.

PowerShell v7
At the moment, of this writing, PowerShell v7 is still a preview.
Since you can install PSv6 and PSv7 side-by-side, you can run your scripts in either version.
This is a handy way to test your scripts for readiness for PSv7.
With the ScriptType parameter, you easily define against which PowerShell version your script should run. First we use powershellv6.
1 2 3 4 5 6 7 8 9 10 11 12 |
$vmName = 'ubuntuVM' $credVM = Get-VICredentialStoreItem -Host $vmName $cred = New-Object System.Management.Automation.PSCredential ($credVM.User, (ConvertTo-SecureString $credVM.Password -AsPlainText -Force)) $sInvoke = @{ VM = $vmName GuestCredential = $cred ScriptText = '$PSVersionTable' ScriptType = 'powershellv6' } Invoke-VMScriptPlus @sInvoke |
This results in.

And then the same, but with powershellv7.
1 2 3 4 5 6 7 8 9 10 11 12 |
$vmName = 'ubuntuVM' $credVM = Get-VICredentialStoreItem -Host $vmName $cred = New-Object System.Management.Automation.PSCredential ($credVM.User, (ConvertTo-SecureString $credVM.Password -AsPlainText -Force)) $sInvoke = @{ VM = $vmName GuestCredential = $cred ScriptText = '$PSVersionTable' ScriptType = 'powershellv7' } Invoke-VMScriptPlus @sInvoke |
And now we get.

Infile/OutFile
The Invoke-VMScriptPlus function captures the output of your script on the stdin stream. But sometimes you would like to produce multiple outputs, or output in a specific format.
The same goes for your input to your script. You can pass input along to your script, for example with a here-string, but this at least requires extra steps to run your script.
For that reason, this v3 version of Invoke-VMScriptPlus, added two new parameters, Infile and OutFile.
Each of these parameters allow you to specify one or more files in your local environment that will be passed and/or retrieved to the environment where your script runs in the guest OS of the target VM.
A somewhat contrived example on what this allows you to do.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
$vmName = 'ubuntuVM' $credVM = Get-VICredentialStoreItem -Host $vmName $cred = New-Object System.Management.Automation.PSCredential ($credVM.User, (ConvertTo-SecureString $credVM.Password -AsPlainText -Force)) $code = @' Get-Content -Path .\in.txt | ForEach-Object -Process { $_ | Set-Content -Path ".\$_.txt" } '@ # Create input file 1..3 | Set-Content -Path .\in.txt Get-ChildItem -Path . -Filter *.txt $sInvoke = @{ VM = $vmName GuestCredential = $cred ScriptText = $code ScriptType = 'powershellv7' InFile = 'in.txt' OutFile = '1.txt', '2.txt', '3.txt' } Invoke-VMScriptPlus @sInvoke Get-ChildItem -Path . -Filter *.txt |
In short, the script creates an input file, then runs a script on the target VM, passing along the input file.
The script on the target VM creates a number of files, which are returned to the caller when the script completes.
This is the output from the above code.

This is a handy feature when your script requires an existing file as input and produces multiple output files.
NoIPinCert
When the function needs to retrieve files from the guest OS, the ESXi node on which the VM is running, provides a URI. This URI normally contains the IP address of the ESXi node.
This can cause an issue when using certificates, and not bypassing the certificate check. The reason is sometimes that the IP address of the ESXi node is not included as a Subject Alternate Address (SAN) in the certificate. This is visible through the following error message. Note that the following output also shows some verbose output to demonstrate when this is happening, and also to show that the error occurs when doing a GET with an URI that contains the IP address.

To avoid this error, the Invoke-VMScriptPlus function, by default, translates this IP address into the FQDN of the ESXi node. The following verbose output shows the adapted URI for the GET

In some environments these automatic conversion of the IP address to the FQDN might cause an issue. One reason for this to happen might be that the ESXi node is added with it’s IP address. Another might be that the DNS query to get the FQDN of the ESXi node fails.
Especially for those occasion, the function now has this NoIPforCert switch. This switch, when set, instructs the function to NOT translate the IP address in the URI to the FQDN.
A sample call that uses this switch can look like this.
1 2 3 4 5 6 7 8 9 |
$sInvoke = @{ VM = $vmName ScriptType = 'bash' ScriptText = 'whoami' GuestCredential = $cred Verbose = $true NoIPinCert = $true } Invoke-VMScriptPlus @sInvoke |
Enjoy!
Todd Ouimet
Hi Luc,
I’m running into a similar issue sylvain posted back on OCTOBER 16, 2020. This works:
$code = @’
hostname.exe
‘@
Invoke-VMScript -ScriptText $code -VM $VMname -GuestCredential $Creds
However trying to run this fails:
$sInvP = @{
VM = $VMname
ScriptType = ‘powershell’
ScriptText = $code
GuestCredential = $Creds
GuestOSType = ‘Windows’
NoIPinCert = $true
}
Invoke-VMScriptPlus @sInvP -Verbose
I get this output:
VERBOSE: 6/15/2023 8:51:39 AM Get-View Finished execution
VERBOSE: 6/15/2023 8:51:39 AM Get-View Finished execution
VERBOSE: 6/15/2023 8:51:39 AM Get-View Finished execution
VERBOSE: 6/15/2023 8:51:39 AM Get-View Finished execution
Exception calling “CreateTemporaryDirectoryInGuest” with “5” argument(s): “A general system error occurred: vix error codes = (1, 0).
”
At line:422 char:104
+ … oraryDirectoryInGuest($moref, $auth, “$($env:USERNAME)_$($PID)”, $nul …
+ ~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : VimException
VERBOSE: Created temp folder in guest OS
VERBOSE: Encountered a problem creating the script file in the guest OS
The user for $Creds is an Admin on the VM and has no issue with logging into the VM. The VM is a Windows 2022 Server. I can run the same $code with Invoke-VMScriptPlus on other Windows 2022 VMs on the same host. All the Windows 2022 VMs on this host are built from the same template and running the same VMware Tools. I’ve tried the VM on other hosts and upgraded the tools to 12.2, no luck.
Any ideas where I should look next?
Thank you!
LucD
Can you check if there is more info in the vpxd log?
Keith
Hi LucD,
Is there any way to troubleshoot why the error running script on guest OS?
I keep receiving this error:
VERBOSE: Encountered a problem running the script file in the guest OS
System.Collections.ArrayList[0].Exception.Message
At C:\Users\User01\Desktop\Scripts\Testing.ps1:518 char:25
+ Throw “$error[0].Exception.Message”
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (System.Collecti…ception.Message:String) [], RuntimeException
+ FullyQualifiedErrorId : System.Collections.ArrayList[0].Exception.Message
This is the only VM I receive this error for. The others have no issues.
This is what I’m running, nothing fancy at all:
$sInvoke = @{
VM = $vmName
GuestUser = ‘root’
GuestPassword = ConvertTo-SecureString -String ‘Password’ -AsPlainText -Force
ScriptText = ‘ifconfig’
ScriptType = ‘bash’
Verbose = $true
}
If I just run Invoke-VMScript -VM $VM -ScriptText ‘ifconfig’ -GuestUser $Usr -GuestPassword $Password
It works just fine, so I am not sure it would be any type of permissions problem.
Thanks
LucD
What kind of Guest OS is that VM running?
What do the verbose messages just before the error say?
Keith
It’s running RHEL 7. For some reason this is the only RHEL 7 VM that I’ve had this problem with.
The Verbose message says:
VERBOSE: 4/24/2023 2:39:00 PM Get-View Finished execution
VERBOSE: 4/24/2023 2:39:00 PM Get-View Finished execution
VERBOSE: 4/24/2023 2:39:00 PM Get-View Finished execution
VERBOSE: 4/24/2023 2:39:00 PM Get-View Finished execution
VERBOSE: No GuestOSType value provided. Trying to determine now.
VERBOSE: It’s a Linux guest OS
VERBOSE: Seems to be a Linux guest OS
VERBOSE: Add SheBang #!/usr/bin/env bash
VERBOSE: Setting up sudo usage
VERBOSE: Created temp folder in guest OS /tmp/vmware-root_1294-2990613108/knoble_12620vmware258
VERBOSE: Created temp script file in guest OS /tmp/vmware-root_1294-2990613108/knoble_12620vmware258/User01_12620vmware179
VERBOSE: Created temp output file in guest OS /tmp/vmware-root_1294-2990613108/knoble_12620vmware258/User01_12620_outputvmware41
VERBOSE: Copying Data to /tmp/vmware-root_1294-2990613108/knoble_12620vmware258/User01_12620vmware179
VERBOSE: PUT https://Host2.local/guestFile?id=244&token=524365fd-263d-4c45-4090-963db8141602244 with -1-byte payload
VERBOSE: received 33-byte response of content type
VERBOSE: Copied scripttext to temp script file
VERBOSE: Make script file executable
VERBOSE: Encountered a problem running the script file in the guest OS
System.Collections.ArrayList[0].Exception.Message
At C:\Users\User01\Desktop\Scripts\Testing.ps1:518 char:25
+ Throw “$error[0].Exception.Message”
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (System.Collecti…ception.Message:String) [], RuntimeException
+ FullyQualifiedErrorId : System.Collections.ArrayList[0].Exception.Message
LucD
Strange.
One option is to add the KeepFiles switch (together with the Verbose switch).
That way the temporary file that contains your script will not be deleted.
You will see the location where the file is located in the Verbose messages.
You could then connect with SSH and check what is wrong with the script, try to run it, and note what eventually goes wrong.
Keith
Thanks for the tip. Turns out I was getting a permission denied error when trying to run the script.
Running “mount | grep noexec” showed that the /tmp directory had the noexec option enabled.
I can run the script by adding the prefix “bash -x ./” or I can modify the script to run it from a different directory. Or just remount the that filesystem and not have to worry about it.
LucD
Thanks for letting me know what was the cause of the issue.
The problem is that the StartProgramInGuest method doesn’t really provide that much feedback (afaik).
I’ll have a look if I could intercept that kind of issue.
Todd Ouimet
Hello Luc,
When attempting to run InFile and OutFile parameters it just throws a bunch of errors. This is the test code (almost exactly like your sample) that I am running:
$code = @’
Get-Content -Path .\in.txt |
ForEach-Object -Process {
$_ | Set-Content -Path “.\$_.txt”
}
‘@
$sInvoke = @{
VM = $VM
ScriptType = ‘powershell’
ScriptText = $code
GuestCredential = $Creds
GuestOSType = ‘Windows’
NoIPinCert = $true
InFile = “D:\Temp\in.txt”
OutFile = ‘1.txt’, ‘2.txt’, ‘3.txt’
}
Invoke-VMScriptPlus @sInvoke
D:\Temp\in.txt file just contains the 1 2 3 just like your sample.
If I comment out the InFile and Outfile and run just a simple Get-Date it connects without any errors.
The 1st error I see is
Exception calling “InitiateFileTransferFromGuest” with “3” argument(s): “File C:\Users\ADMINI~1\AppData\Local\Temp\admin_12832vmware170/1.txt was not found”
LucD
Strange.
Can you add the Verbose switch?
That might show some more info about the issue
Todd Ouimet
Ok that revealed what was going on….
I changed the following and it’s working now.
Line 446:
From: $destinationFilePath = “$tempFolder/$(Split-Path -Path $_ -Leaf)”
To: $destinationFilePath = “$tempFolder\$(Split-Path -Path $_ -Leaf)”
Line 553:
From: $sourceFilePath = “$tempFolder/$_”
To: $sourceFilePath = “$tempFolder\$_”
I suppose this would break any attempts to use InFile & OutFile with Linux now.
LucD
Not really.
I can use the GuestOSType parameter to have different separators depending on Linux or Windows.
If the user does not use the GuestOSType, the code tries to find out what the Guest OS type is.
I’ll do some tests and update the code.
Todd Ouimet
Hi Luc!
I may have uncovered a bug in Invoke-VMScriptPlus V3 when connected to multiple vCenters despite providing the vCenter instance to the -Server parameter. I added “-Server $Server” to lines 285 – 288 which resolved the “The object ‘vim.VirtualMachine:vm-772’ has already been deleted or has not been completely created” error.
$si = Get-View ServiceInstance -Server $Server
$guestMgr = Get-View -Id $si.Content.GuestOperationsManager -Server $Server
$gFileMgr = Get-View -Id $guestMgr.FileManager -Server $Server
$gProcMgr = Get-View -Id $guestMgr.ProcessManager -Server $Server
Without the “-Server $Server” I noticed the above variables were getting populated with multiple values per each connected vCenter.
Thank for the this wonderful script and all you do for the PowerCLI community!!
LucD
Thanks Todd, well spotted.
I think only adding the -Server $Server parameter on line 285 should do the trick as well.
That way you only have 1 object in the $si variable.
Todd Ouimet
I started with just that and still continued to get the error so I slapped it on all four and it worked.
LucD
I’ll do some tests, and update the code if needed.
Lucas
Howdy Luc,
I ran into an issue using InFile on line 446. There is a forward slash between $tempFolder and the output of Split-Path. The problem is that this would prepend the file with the $tempFolder string followed by “!%”, then the InFile would be dumped into the parent directory of the working directory of the guest from which the script text is being executed. The solution was to change the forward slash to a back slash. I am running PowerShell 7.2.4 for what it’s worth.
Cheers!
Lucas
LucD
Hi Lucas,
I’m not sure where that “!%” you mention is coming from.
Are you running this on a Windows, Linux or Mac station?
Could you perhaps show how you did call Invoke-VMScriptPlus and how you used the InFile parameter?
Lucas
I am running this from a Windows station, targeting a Windows guest. Invoke-VMScriptPlus is ran like this:
$hInvoke = @{
VM = “`$_”
GuestCredential = $SITE_ADMIN_CREDENTIAL
ScriptText = $do
InFile = ‘.\in_files\servicesInfile.json’
ScriptType = ‘powershell’
NoIPinCert = $true
BehindNAT = $true
}
$vms | ForEach-Object{
Invoke-VMScriptPlusNAT @hInvoke
}
As you can see, I do have a modifier here as I created a switch to modify the IP address of the ESXi node in the Uri since I am targeting nodes behind a NAT IP. Currently the NAT address is baked into the module; I will change that soon so that it accepts an address from whatever environment is targeted. Could that be causing the issue? I do not believe so since the file does in fact make it to the guest OS, but with the working directory name prepended to the actual file name followed by a “!%” and changing the forward slash to a backslash in the file path creation seemed to help.
Todd
Hi Luc,
Again thankyou so much for such an awesome and super useful script! It’s been running flawlessly for nearly ~3 months until I updated all the VCSA’s to the latest 6.7U3 version to address the recent security issue. Also I updated PowerCLI to the latest version as well.
Those are the only thing that changed in the environment and the script I used your Invoke-VMScriptPlus function in has remained unchanged.
Now when I invoke it I get the following error messages:
Select-Object : Property “NameHost” cannot be found.
Resolve-DnsName -Name $ip | Select-Object -ExpandProperty NameHost
Invoke-WebRequest : Cannot bind parameter ‘Uri’. Cannot convert value “https://:443/guestFile?id=514&token=52d5768f-6378-1ebf-5a8b-30299b9009c4514”
I’m passing the info to Invoke-VMScriptPlus like this…
$sInvP = @{
VM = $VM
ScriptType = ‘powershell’
ScriptText = $code
GuestCredential = $MasterLocalAdminCreds
GuestOSType = ‘Windows’
NoIPinCert = $true
}
$result = Invoke-VMScriptPlus @sInvP
I validated @sInvP contains correct info.
It would appear some how the script no longer is receiving the $VM name. Any ideas?
Thank you so much!!!!
LucD
Hi Todd,
It looks like you discovered an error in that code.
That property NameHost should in fact be just Name.
Can you check if that fixes the issue you are getting?
Luc
E.D.
Hi Luc,
Really cool script, thank you for it!
I never had the need to use classes in my PS scripts, I forced myself into learning classes (which is a good thing) so I understand your script, luckily the syntax is the same as in C#, only the ‘grammar’ looks different, so thankfully it was not as harder than learning totally new concepts.
Additionally, thank you, your script made me learn something completely new: Transformation Attributes
I found a small bug in your code, line 18 “$($inputData)” if the VM has a space in its name, your script will fail – I had a machine named like his ‘VM -Test01’ – which caused the script to try to execute ‘Get-VM VM -Test01’, the ‘-Test01’ was treated like a cmdlet parameter, what I did on my end to fix this was to change it from:
$cmdParam = “-$(if($this.Type -eq ‘VIServer’){‘Server’}else{‘Name’}) $($inputData)”
to:
$cmdParam = “-$(if($this.Type -eq ‘VIServer’){‘Server’}else{‘Name’}) ‘$inputData'”
I put single quotation marks around it – BTW I got rid of the ‘$()’ – out of habit.
Line 34 which is for array input objects, I guess would have to go through the same process – I have not checked/tested this as it did not apply to my case.
LucD
Thanks for finding that “feature” 😉
I’ll update the code.
sylvain
Hi Luc,
thanks for the new version.
However, I hit some issue when running your script against vcenter 6.5 to 6.7 u3.
I installed the latest powercli 12.1.xxx
powershell version 5.1 on machine executing the script.
Your script is located on a file share, target machine tested are windows 2016-2019
Error message is the following :
VERBOSE: 10/16/2020 10:59:37 AM Get-View Finished execution
Exception calling “CreateTemporaryDirectoryInGuest” with “5” argument(s): “The object ‘vim.VirtualMachine:vm-48032’ has already been deleted or has not been completely created”
At \\..\Invoke-VMScriptPlus.ps1:382 char:104
+ … oraryDirectoryInGuest($moref, $auth, “$($env:USERNAME)_$($PID)”, $nul …
+ ~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : VimException
VERBOSE: Created temp folder in guest OS
VERBOSE: Encountered a problem creating the script file in the guest OS
[0].Exception.Message
At \\..\Invoke-VMScriptPlus.ps1:404 char:17
+ Throw “$error[0].Exception.Message”
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: ([0].Exception.Message:String) [], RuntimeException
+ FullyQualifiedErrorId : [0].Exception.Message
Advice is welcome, thank you for your help !
LucD
The error message seems to indicate the issue might be with that specific VM.
Do you get the same error for other VMs?
Sylvain
Yes, and for the same machine, original invoke-vmscript works properly
LucD
Do you have some further info on the Guest OS?
And is that the same or different for the other VMs where you encounter the issue?
Is the temporary folder created in the Guest OS?
You should see the path in the verbose message.
If the folder is there, can you create a file in there?
Logon with the same account as you use for the GuestCredential
Sylvain
Guest OS is windows 2019-build 1809,
executionPolicy set to unrestricted,
uac disabled,
machine joined to the domain,
account used is in admin group,
Temp folder and file created but file is empty.
By the way, I was connected to 3 different vcenter, by dropping 2 of them, I get now another error :
VERBOSE: Copying Data to C:\Users\xx\AppData\Local\Temp\xx_8840vmware248\xx_8840vmware209.ps1
VERBOSE: PUT https://1.2.3.4/guestFile?id=32&token=52f58ee5-7891-e797-e36a-df66688972fa32 with -1-byte payload
Invoke-WebRequest : The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
At \\..\Invoke-VMScriptPlus_new.ps1:223 char:27
+ … copyResult = Invoke-WebRequest -Uri $fileInfo -Method Put -Body $Data
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
ScripText copy failed!Status
At \\..\Invoke-VMScriptPlus_new.ps1:226 char:17
+ … Throw “ScripText copy failed!`rStatus $($copyResult.Statu …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (ScripText copy failed!Status :String) [], RuntimeException
+ FullyQualifiedErrorId : ScripText copy failed!Status
Find the powercliConfiguration :
Scope ProxyPolicy DefaultVIServerMode InvalidCertificateAction DisplayDeprecationWarnings WebOperationTimeout
Seconds
—– ———– ——————- ———————— ————————– ——————-
Session NoProxy Multiple Ignore False -1
User NoProxy Multiple Ignore False -1
AllUsers NoProxy Multiple Ignore False -1
by forcing to trust all certificates with the following command :
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } | out-null
[Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12 | out-null
if(-not(“TrustAllCertsPolicy” -as [type])){
add-type @”
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
“@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy;
}
I get another new error :
VERBOSE: Copied scipttext to temp script file
VERBOSE: Encountered a problem running the script file in the guest OS
[0].Exception.Message
At \\..\Invoke-VMScriptPlus_new.ps1:528 char:25
+ Throw “$error[0].Exception.Message”
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: ([0].Exception.Message:String) [], RuntimeException
+ FullyQualifiedErrorId : [0].Exception.Message
ps1 file contains now the command, but output is empty
LucD
Did you use the NoIPinCert switch?
Sylvain
tried both, same behavior
by looking more closely at the exception, I can see the follownig :
$error[0].Exception
for ($i = 0; $Exception; $i++, ($Exception = $Exception.InnerException))
{ “$i” * 80
$Exception | Format-List * -Force
}
00000000000000000000000000000000000000000000000000000000000000000000000000000000
ErrorRecord : Exception calling “StartProgramInGuest” with “3” argument(s): “A specified parameter was not correct: ”
WasThrownFromThrowStatement : True
Message : Exception calling “StartProgramInGuest” with “3” argument(s): “A specified parameter was not correct: ”
Data : {}
InnerException :
TargetSite :
StackTrace :
HelpLink :
Source :
HResult : -2146233087
LucD
Ok, I’ll have to do some further debugging on that.
Can you share the Invoke-VMScriptPlus line you used?
And the script you were trying to run?
If you don’t want to post it here, you can send it to me via the Contact Form on the website.
Sylvain
Well no problem.
The call :
Invoke-VMScriptPlus -vm (get-vm -name “vmdc*” -Server $sgdcvc) -scriptText “get-service” -server $sgdcvc -ScriptType powershell -GuestCredential $cred -Verbose -scriptenvironment “Windows” -GuestOSType Windows
Sylvain
Hi Luc,
Were you able to find something new about that behavior ?
Thank you for your feedback
LucD
No
Sylvain
Hey Luc,
Quick update, your function works fine with powercli 10.1 against vcenter 6.7u3 even if this powercli version is not officially supported with that vcenter version
Sylvain
Sorry stacktrace shows more thing
$StackTrace
Server stack trace:
at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at VimApi_70.VimPortType.StartProgramInGuest(StartProgramInGuestRequest request)
at VMware.Vim.GuestProcessManager.StartProgramInGuest(ManagedObjectReference vm, GuestAuthentication auth, GuestProgramSpec spec)
Spex
Hi LucD, first thanks for sharing your work. I had some trouble using your function:
– we have vms with execution policy remote signed. I would like to see a switch for powershell to start the scripts with policy unrestricted or bypass
– using in and out file has some problems since you use “/” in concatenation of path and files. This does not work. In windows you have to use “\”. Problem exists for in and for out-
– the outfile option only works with filenames without path. This should be documented.
– where is the console output saved to – in verbose mode I can see the download of the outlogfile, but I can not find the file.
– for problems with SSL/TLS there should be a switch to ignore them (I use a scriptblock from someone else to completly ignore certificate problems). The parameter noipincert is nice but not allways enough.
Greetings, Spex
LucD
Thanks for that valuable feedback.
I will see if I can implement some of those suggestions and observations.
Macd
the invoke-vmscriptplusv3 download url is still 2019, would you put the latest file? Thanks
LucD
That is correct, there have been some minor updates (see notes at the start of the article).
But the URL is still the same one.
Sri
Hi Lucd,
I have tried to run TLS reconfig script (python) using invoke-vmscriptplus but I am getting invalid syntax error. Am I doing any thing wrong ? Could you please advice what colud be the issue here
Invoke-VMScriptPlus -VM “VMNAME” -ScriptText ‘/usr/lib/vmware-vSphereTlsReconfigurator/VcTlsReconfigurator/reconfigureVc scan’ -GuestUser “xx”” -ScriptType python3 -SkipCertificateCheck -GuestOSType linux
Here is the output
ScriptType : python3
ScriptText : #!/usr/bin/env python3
/usr/lib/vmware-vSphereTlsReconfigurator/VcTlsReconfigurator/reconfigureVc scan
GuestOS : linux
Start : 8/12/2020 2:57:42 AM
ScriptSize : 102
Finish : 8/12/2020 2:57:43 AM
ExitCode : 1
Pid : 41709
ScriptOutput : File “/tmp/vmware-root_1374-2689143812/xxx_10928vmware52/xxx_10928vmware79”, line 2
/usr/lib/vmware-vSphereTlsReconfigurator/VcTlsReconfigurator/reconfigureVc scan
^
SyntaxError: invalid syntax
LucD
Hi,
I’m not sure what is causing this I’m afraid.
Could you add the Verbose switch to the call, that might provide some more information.
Another option is to use the KeepFiles switch.
That way the files, script & output, will not be removed in the Guest OS.
The Verbose switch should tell you which are those files.
Then you could login to the Guest OS and try to run the script file from the prompt.
That sometimes provides more feedback on what goes wrong.
Luc
Sri
Hi Lucd,
Thanks for your reply. I missed to check this as I got work around to fix this. Here is the Verbose output with invoke-vmscriptplus
$stext = “/usr/lib/vmware-vSphereTlsReconfigurator/VcTlsReconfigurator/reconfigureVc scan”
Invoke-VMScriptPlus -VM $VM -ScriptText $stext -GuestCredential $cred -ScriptType python3 -GuestOSType Linux -SkipCertificateCheck -Verbose
VERBOSE: 9/4/2020 7:57:09 PM Get-View Finished execution
VERBOSE: 9/4/2020 7:57:09 PM Get-View Finished execution
VERBOSE: 9/4/2020 7:57:09 PM Get-View Finished execution
VERBOSE: 9/4/2020 7:57:09 PM Get-View Finished execution
VERBOSE: Seems to be a Linux guest OS
VERBOSE: Add SheBang #!/usr/bin/env python3
VERBOSE: Created temp folder in guest OS /tmp/vmware-root_1376-2689143845/padvmengnp02_10756vmware52
VERBOSE: Created temp script file in guest OS /tmp/vmware-root_1376-2689143845/padvmengnp02_10756vmware52/user02_10756vmware105
VERBOSE: Created temp output file in guest OS /tmp/vmware-root_1376-2689143845/user02_10756vmware52/user02_10756_outputvmware189
VERBOSE: Copying Data to /tmp/vmware-root_1376-2689143845/user02_10756vmware52/user02_10756vmware105
VERBOSE: PUT https://172.29.231.86/guestFile?id=31&token=52e2ef9b-7116-bcde-2f41-8a3a4936d33d31 with 102-byte payload
VERBOSE: received 33-byte response of content type
VERBOSE: Copied scripttext to temp script file
VERBOSE: Make script file executable
VERBOSE: Run script with ‘/tmp/vmware-root_1376-2689143845/user02_10756vmware52/user02_10756vmware105 > /tmp/vmware-root_1376-2689143845/user02_10756vmware52/user02_10756_outputvmware189’
VERBOSE: Wait for process to end
VERBOSE: Get output from /tmp/vmware-root_1376-2689143845/user02_10756vmware52/user02_10756_outputvmware189
VERBOSE: GET https://172.29.231.86/guestFile?id=31&token=527d8657-3103-7203-37a6-97765cf4ca1a31 with 0-byte payload
VERBOSE: received 223-byte response of content type
VERBOSE: Removed folder /tmp/vmware-root_1376-2689143845/user02_10756vmware52
ScriptText : #!/usr/bin/env python3
/usr/lib/vmware-vSphereTlsReconfigurator/VcTlsReconfigurator/reconfigureVc scan
ExitCode : 1
Start : 9/4/2020 11:57:09 PM
Pid : 46761
ScriptOutput : File “/tmp/vmware-root_1376-2689143845/user02_10756vmware52/user02_10756vmware105”, line 2
/usr/lib/vmware-vSphereTlsReconfigurator/VcTlsReconfigurator/reconfigureVc scan
^
SyntaxError: invalid syntax
GuestOS : Linux
PidOwner : root
ScriptType : python3
ScriptSize : 102
OutFiles :
VM : VMNAMe
Finish : 9/4/2020 11:57:10 PM
This code works for me, I followed some steps mentioned in this article https://www.virtuallyghetto.com/2016/02/how-to-remotely-run-appliancesh-other-shell-commands-on-vcsa-wo-requiring-ssh.html
Code Working
===========
foreach ($vc in $vcs){
Write-Output “Scanning TLS Versions on $vc”
Invoke-VMScript -ScriptText “export VMWARE_VAPI_HOME=/usr/lib/vmware-vapi
export VMWARE_RUN_FIRSTBOOTS=/bin/run-firstboot-scripts
export VMWARE_DATA_DIR=/storage
export VMWARE_INSTALL_PARAMETER=/bin/install-parameter
export VMWARE_LOG_DIR=/var/log
export VMWARE_OPENSSL_BIN=/usr/bin/openssl
export VMWARE_TOMCAT=/opt/vmware/vfabric-tc-server-standard/tomcat-7.0.55.A.RELEASE
export VMWARE_RUNTIME_DATA_DIR=/var
export VMWARE_PYTHON_PATH=/usr/lib/vmware/site-packages
export VMWARE_TMP_DIR=/var/tmp/vmware
export VMWARE_PERFCHARTS_COMPONENT=perfcharts
export VMWARE_PYTHON_MODULES_HOME=/usr/lib/vmware/site-packages/cis
export VMWARE_JAVA_WRAPPER=/bin/heapsize_wrapper.sh
export VMWARE_COMMON_JARS=/usr/lib/vmware/common-jars
export VMWARE_TCROOT=/opt/vmware/vfabric-tc-server-standard
export VMWARE_PYTHON_BIN=/opt/vmware/bin/python
export VMWARE_CLOUDVM_RAM_SIZE=/usr/sbin/cloudvm-ram-size
export VMWARE_VAPI_CFG_DIR=/etc/vmware/vmware-vapi
export VMWARE_CFG_DIR=/etc/vmware
/usr/lib/vmware-vSphereTlsReconfigurator/VcTlsReconfigurator/reconfigureVc scan
” -vm $vc -Guestcredential $gcred
}
LucD
I suspect the issue might be caused by line breaks in your ScriptText.
You could try to define the ScriptText in a here-string.
Zafer
Lucd Hi, I have some problems converting my scripts into workflow architecture. I’ve solved many of them but except the ones that I use your Invoke-VMScriptPlus function/script.
I have added your function into my script and I can call it from anywhere inside the script. But in the same script I I have a workflow and inside that workflow I have a inlinescript part. When I use invoke-vmscriptplus inside that inlinescript I receive error that invoke-vmscriptplus is not recognized as commandlet or function. I’ve read many articles that says inside the workflow any function can be called in workflow scope or parent scope (which has invoke-vmscriptplus function) but I cannot make it work. Do you have any suggestion?
Todd
Any suggestions on how to utilize your Invoke-VMScriptPlus v3 with Start-Job? Specifically how to pass the function including the class into Start-Job. I’ve tried to tie it together with another one of your posts without any success.
https://www.lucd.info/knowledge-base/running-a-background-job/
LucD
Hi Todd,
I’m not exactly sure what you are trying to do.
Is it the intention to call Invoke-VMScriptPlus from within a background job (started with Start-Job)?
You can just pass the class + function in the code you run via Start-Job.
Or save it in a file that you could dot-source from within the code you run via Start-Job.
If that is not what you want to do, can you provide some more details?
If you want, use the Contact Form to keep your information more private.
Luc
Todd
Hi Luc!!
Just tried the contact form from two separate systems. I get this message: There was an error trying to send your message. Please try again later.
Anyways…Exactly right, call Invoke-VMScriptPlus within the Start-Job. I have a long running process I’m performing on multiple systems by utilizing your AWESOME function Invoke-VMScriptPlus that I’d like to run in parallel. Basically, I have a main script I want to run SDelete.exe on several template VMs in parallel as jobs. When the jobs are completed the main script will continue with the additional items I want to complete on the templates.
This runs great:
$VMname = “W2019-Temp”
$code = @’
C:\Admin\SDelete\sdelete64.exe -z C:
‘@
$sInvP = @{
VM = $VMname
ScriptType = ‘bat’
ScriptText = $code
GuestCredential = $LocalAdminCreds
GuestOSType = ‘Windows’
NoIPinCert = $true
SkipCertificateCheck = $true
}
Invoke-VMScriptPlusV3 @sInvP -Verbose
However trying to figure out what the syntax would look like to pass the class + function into the Start-Job using InitializationScript
I thought about dot sourcing it (Invoke-VMScriptPlus) from a file, which I may just end up with that but really wanted it all to be self-contained into one script.
Anyways, THANK YOU SO MUCH for all you do for the PowerCLI community. I’ve re-used your code to accomplish so much over the years. You are a treasure to the community!
LucD
Thanks for notifying me, there is indeed an issue with the Contact Form.
On your question; it is just a matter of packing all the required code in a scriptblock that you pass to Start-Job.
For example
# The scriptblock you pass to Start-Job
$scriptBlock = {
param(
[string]$Server,
[string]$SessionId,
[string]$VMName
)
# <== Insert lines with class and Invoke-VMScriptPlus function # As an alternative dot-source a .ps1 file holding those lines Set-PowerCLIConfiguration -DisplayDeprecationWarnings $false -Confirm:$false | Out-Null Connect-VIServer -Server $Server -Session $SessionId # Your script $code = @’ C:\Admin\SDelete\sdelete64.exe -z C: ‘@ # !!! Inline string terminator has to start in column 1 # Set up $LocalAdminCreds (you could pass user/pswd as arguments to Start-Job) $LocalAdminCreds = '
'
$sInvP = @{
VM = $VMname
ScriptType = ‘bat’
ScriptText = $code
GuestCredential = $LocalAdminCreds
GuestOSType = ‘Windows’
NoIPinCert = $true
SkipCertificateCheck = $true
}
Invoke-VMScriptPlusV3 @sInvP -Verbose
}
$vmName = 'W2019-Temp'
$sJOb = @{
ScriptBlock = $scriptBlock
ArgumentList = $global:DefaultVIServer.Name, $global:DefaultVIServer.SessionId, $vmName
}
Start-Job @sJob
Shibin
When I run it through Windows 2016 host with PowerCLI 11.5, got following error:
Invoke-WebRequest : The response content cannot be parsed because the Internet Explorer engine is not available, or Internet
Explorer’s first-launch configuration is not complete. Specify the UseBasicParsing parameter and try again.
At C:\Users\maverick\Documents\Invoke-VMScriptPlus.ps1:223 char:27
+ … copyResult = Invoke-WebRequest -Uri $fileInfo -Method Put -Body $Data
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotImplemented: (:) [Invoke-WebRequest], NotSupportedException
+ FullyQualifiedErrorId : WebCmdletIEDomNotSupportedException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
ScripText copy failed!Status
At C:\Users\maverick\Documents\Invoke-VMScriptPlus.ps1:226 char:17
+ … Throw “ScripText copy failed!`rStatus $($copyResult.Statu …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (ScripText copy failed!Status :String) [], RuntimeException
+ FullyQualifiedErrorId : ScripText copy failed!Status
LucD
Sorry to hear that.
This seems to be a known issue with Invoke-WebRequest.
Can you try changing lines 243-247 to
$sWeb = @{
Uri = $fileInfo
Method = 'Put'
Body = $Data
}
to
$sWeb = @{
Uri = $fileInfo
Method = 'Put'
Body = $Data
UseBasicParsing = $true
}
and lines 282-285
$sWeb = @{
Uri = $fileInfo.Url
Method = 'Get'
}
to
$sWeb = @{
Uri = $fileInfo.Url
Method = 'Get'
UseBasicParsing = $true
}
If that solves the issue, I will update the post accordingly.
Dave
I was getting errors with line 297,
“VERBOSE: received 295-byte response of content type
Set-Content: C:\Users\dave.moreland\invoke-vmscriptplus.ps1:297
Line |
297 | $fileContent.Content | Set-Content -Path $File -Encoding byte -Confir …
| ~~~~
| Cannot process argument transformation on parameter ‘Encoding’. ‘byte’ is not a supported encoding
| name. For information on defining a custom encoding, see the documentation for the
| Encoding.RegisterProvider method. (Parameter ‘name’)”
I had to change mine to look like this:
“$fileContent.Content | Set-Content -Path $File -Encoding oem -asbytestream -Confirm:$false”
I’m running scripts on centos 7, have not tested elsewhere,
Thank you for the script,
Dave
LucD
Thanks Dave.
With the multitude of available platforms the function will probably need to determine the encoding or offer an option to specify one.
I’ll look into it.
Dion
Thanks for v3
Have you been able to get this to work in powershell Core for Linux? I have been trying to get it working on Core 6.x and 7.0 preview and get these errors:
6.x:
ERROR The remote certificate is invalid according to the validation procedure.
7.x preview:
Exception calling “CreateTemporaryDirectoryInGuest” with “5” argument(s): “Failed to authenticate with the guest operating system using the supplied credentials.”
I tried adding
Set-PowerCLIConfiguration -InvalidCertificateAction ignore -confirm:$false
but still receiving same issue.
Cody
Yes, I am trying to do PS Core in Linux and am receiving the same thing. Please update accordingly!
LucD
Which Linux version do you have as the Guest OS?
And which vSphere version (ESXi & VCSA)?
Dave Benayoun
Hey Luc love your scripts! Quick question though any chance of adding something similar to invoke-command $using: so that I can pass my local variables to the session?
Thanks,
Dave
LucD
Hi Dave,
There is, unfortunately, no ‘connection’ between the station where you run Invoke-VMScriptPlus and the script running on the target station.
Unlike the PS remote sessions where that kind of connection is present.
The best substitute, imho, is to substitute your local variables in the code you sent, before calling Invoke-VMScriptPlus.
Have a look at my Here string and variable substitution and Here strings and the ExpandString method posts.
Hope this helps,
Luc