Hello I'm having a bit of problem with the Invoke-VMScript cmdlet
I have made a script that creates a virtual Windows 7 machine and then some powershell script executes on the machine for example renaming the computer to the correct name.
But if I run
Invoke-VMScript -ScriptText {(Get-Wmiobject -Class Win32_ComputerSystem).Rename($strName)}
The $strName variable doesn't get resolved, anyone have any idea on how to do this?
Your haven't correctly scoped your variable. Here's a simple experiment you can try in your console:
PS C:\> $test = "Resolve!"
PS C:\> $test
Resolve!
# This scriptblock will not resolve the variable.
PS C:\> {$test}
$test
# This scriptblock will resolve the variable.
PS C:\> [scriptblock]::Create($test)
Resolve!
Invoke-VMScript's documentation suggests you should pass -ScriptText as a string instead of a ScriptBlock. So in this case, we can mimic example 2:
$string = "(Get-Wmiobject -Class Win32_ComputerSystem).Rename($strName)"
Invoke-VMScript -ScriptText $string
Variables enclosed by " " will be resolved.
I don't have a guest handy for renaming, but following LucD, here's a working example using variables to build up input for scriptText:
$svcName = "vmtools"
$guestScript = 'get-wmiObject win32_service | where {$_.name -eq "' + $svcName + '"}'
Invoke-VMScript -vm myVMname -scriptText $guestScript
#Rename Computer command line script
$renamecomputer = "wmic path win32_computersystem where ""Name='%computername%'"" CALL rename name='$VM'"
#Send command line script to GuestOS
Invoke-VMScript -VM $VM -GuestUser $GU -GuestPassword $GP -ScriptType Bat -ScriptText $renamecomputer
This is what i use for my renaming script.
Works well
I got this working:
Invoke-VMScript -VM $vm -ScriptText "(Get-WmiObject win32_computersystem).rename(""${vmName}"")"
Related
I'm trying to parallelize a portion of a bigger PWSH script, basically I need to cycle through a list of VMs and execute some commands on them, and to speed up the process, I need this to be executed in parallel:
#Utils.ps1 contains all the functions
.(".\src\utils.ps1")
# List of VMs on which we execute the space reclaim
$VMs = Get-Content .\vmnames.txt
# Check if directory ./Results exists, if not it will be created
CheckDirResults
# Check if dir temp exists, if not it will be created
CheckDirTemp
# Asks for vCenter credentials
GetVServerCred
# Asks for VM credentials
GetVMCred
# Connects to vCenter
ConnectVcenter
# Commands that will be executed on VMs on line 94 (Invoke-VMScript)
$InvokeScript = #'
#! /bin/bash
pwd
'#
foreach ($vm in $VMs) {
$vms = Get-VM -Name $vm
$vms | ForEach-Object -Parallel {
Write-Output "Testing on $_"
Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $InvokeScript -Confirm:$false
}
}
I also tried to simply execute the Invoke directly on $VMs like this:
$VMs | ForEach-Object -Parallel {
Write-Output "Testing on $_"
Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $commands
}
In both cases, the Invoke-VMScript can't either connect to the VMs or can't find the chunk of code to execute on the VMs.
Errors:
"test.ps1" 44L, 934C 36,0-1 81%
---- ---- ----
evl6800756.sys.ntt.eu 443 VSPHERE.LOCAL\Administrator
Testing on euczdrpoc03
Testing on euczdrpoc30
Invoke-VMScript:
Line |
3 | Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1/17/2023 10:42:02 AM Invoke-VMScript You are not currently connected to any servers. Please connect first using a Connect cmdlet.
Invoke-VMScript:
Line |
3 | Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1/17/2023 10:42:02 AM Invoke-VMScript Value cannot be found for the mandatory parameter ScriptText
Invoke-VMScript:
Line |
3 | Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1/17/2023 10:42:02 AM Invoke-VMScript You are not currently connected to any servers. Please connect first using a Connect cmdlet.
Invoke-VMScript:
Line |
3 | Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1/17/2023 10:42:02 AM Invoke-VMScript Value cannot be found for the mandatory parameter ScriptText
The $using: scope modifier inside a -Parallel script-block lets you refer to variables assigned a value outside that script-block. Examples in your question are not complete enough to say for certain, but hopefully this explains at least some of the difficulty.
So for example within the parallel block, try $using:commands instead of your $commands. This bit from reference doc could be more helpfully spelled out, IMHO.
Use the $using: keyword to pass variable references to the running
script.
That's demonstrated more clearly in one of the reference examples, in this blog post, and this answer.
Edit: sounds like the parallel runspaces do not inherit the vCenter connection. The open proposal for a -UseCurrentState switch to transfer the current runspace state to -Parallel loop iterations might help in your situation. Meanwhile could try passing -Server $using:DefaultVIServer to relevant commands inside the parallel block. LucD's comment that PowerCLI is not threadsafe may also be worth researching alongside the note in the reference doc:
The ForEach-Object -Parallel parameter set runs script blocks in parallel on separate process threads. The $using: keyword allows passing variable references from the cmdlet invocation thread to each running script block thread. Since the script blocks run in different threads, the object variables passed by reference must be used safely. Generally it is safe to read from referenced objects that don't change. But if the object state is being modified then you must used thread safe objects, such as .NET System.Collection.Concurrent types
I managed to do it this way:
# Commands that will be executed on VMs on line 94 (Invoke-VMScript)
$commands = #'
#! /bin/bash
hostname
'#
# Loads the VMs names from the file vmnames.txt
$vms = Get-Content ".\vmnames.txt"
# Uses the content of the file vmnames.txt to get the VM objects
$vmnames = Get-VM -Name $vms
# Executes the commands on the VMs
$vmnames | ForEach-Object -Parallel {
# Loads the variables in the parallel session
$commands = $using:commands
$VMCred = $using:VMCred
Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $commands
}
For some reasons, piping directly $vms (so the content of the file to ForEach-Object -Parallel won't pass correctly the value (in this case the VM name), while using the Get-VM -Name $vms will do.
Then, once this is solved the ForEach-Object -Parallel will finally get the correct VM name assigned to $_.
I have a script that modifies network info of certain VMs ,however it run sequentially . Since there are hundred of VMs , I want to all VMs to execute the command simultaneously in order to save time . Also how do I add invoke-vmscript to the scriptblock with external variables. I did try few things but doesnt seem to working well. Script is run on a system using powercli 10 on linux and no additional modules are allowed to be installed
Below is the code , any help would be much appreciated
$test=get-vm -name testvm* |sort-object
$fip='192.168.10.41'
$f=$fip.Split(".")|select -first 3
$s=[system.String]::join(".",$f)
$l=$fip.Split(".")|select -last 1
foreach ($vm in $test) {
Write-Host "$vm $s.$l"
do {start-sleep -s 2; write-host " $vm not ready yet" ; $tstatus=(get-vm
$vm).extensiondata.Guest.ToolsStatus;}while($tstatus -ne "toolsOk");
invoke-vmscript -ScriptText "sed -i '14,20 d' /etc/network/interfaces" -
ScriptType bash -VM $vm -GuestUser admin -GuestPassword Welcome!}
$ipscript = "sed -i -e 's/address *.*.*.*/address $s.$l/g'
/etc/network/interfaces"
write-host " Adding IP "
invoke-vmscript -ScriptText $ipscript -ScriptType bash -VM $vm -GuestUser
$GuestUserName admin -GuestPassword Welcome!
Start-Sleep -s 2
Restart-VM -VM $vm -RunAsync -Confirm:$false
$l=[int]$l+1
}
it runs the script up until the if clause (for running url) I cannot include the other url here but the script is
"welcome, want to go to site?"
$goyn=read-host -prompt "enter y or n"
if($goyn -eq 'y'){
start-process -Filepath chrome.exe -ArgumentList www.google.ca
}
else{"continue on your journey of awesomeness"}
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Get-WmiObject -Class Win32_OperatingSystem -ComputerName localhost |
Select-Object -Property CSName,#{n="Last Booted";
e={[Management.ManagementDateTimeConverter]::ToDateTime($_.LastBootUpTime)}}
for the start-process it throws the error "cannot find file specified followed by the path for $profile. wanted a quicker way of of getting to my work service page.
Specify full path to chrome.exe and other used items. E.g. C:\Program Files (x86)\Google\Application\chrome.exe. Adjust %PATH% otherwise. But I'd better specify full path.
So figured it out. it did not like start-process not one bit no matter how outrageously obvious I made the path. So I used the start cmdlet instead and put site in single quotes. Code looks like this:
"welcome, want to go to site?"
$goyn=read-host -prompt "enter y or n"
if($goyn -eq 'y'){
start 'www.google.ca'
}
else{"continue on your journey of awesomeness"}
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Get-WmiObject -Class Win32_OperatingSystem -ComputerName localhost |
Select-Object -Property CSName,#{n="Last Booted";
e={[Management.ManagementDateTimeConverter]::ToDateTime($_.LastBootUpTime)}}
I am using Powershell 4.0 on a remote computer (rem_comp) to access another one (loc_comp; Powershell 2.0 installed here) in order to get the number of files listed without folders:
$var1 = 'H:\scripts'
Invoke-Command -Computername loc_comp -scriptblock {(Get-Childitem $var1 -recurse | Where-Object {!$_.PSIsContainer}).count}
However when using $var1 inside -scriptblock, it does not deliver anything (neither any error message).
When using
$var1 = 'H:\scripts'
Invoke-Command -Computername loc_comp -scriptblock {(Get-Childitem $ -recurse | Where-Object {!$_.PSIsContainer}).count}
it works!
Note: Changing var1 from ' to " does not help.
Running the command without Invoke-Command locally faces the same problem.
How to fix this?
To complement CmdrTchort's helpful answer:
PS v3 introduced the special using: scope, which allows direct use of local variables in script blocks sent to remote machines (e.g., $using:var1).
This should work for you, because the machine you're running Invoke-Command on has v4.
$var1 = 'H:\scripts'
Invoke-Command -Computername loc_comp -scriptblock `
{ (Get-Childitem $using:var1 -recurse | Where-Object {!$_.PSIsContainer}).count }
Note that using: only works when Invoke-Command actually targets a remote machine.
When you're using Invoke-command and a script-block , the scriptblock cannot access your params from the outer scope (scoping rules).
You can however, define the params and pass them along with the -Argumentlist
Example:
Invoke-Command -ComputerName "localhost" {param($Param1=$False, $Param2=$False) Write-host "$param1 $param2" } -ArgumentList $False,$True
The following should work for your example:
Invoke-Command -Computername loc_comp -scriptblock {param($var1)(Get-Childitem $var1 -recurse | Where-Object {!$_.PSIsContainer}).count} -ArgumentList $var1
Simple question but not able to find answer on google at the moment. My powershell version is 2. I want to flush and registerdns on multiple machines.
ipconfig /flushDns
ipconfig /registerdns
I can't use invoke command and psremoting is not enabled on machines.
Any advise how to flushdns & registerdns.
It's pretty easy with Invoke-wmimethod
Create a list of your computers in a file named servers.txt, then create a script like this :
$listofservers = Get-Content .\servers.txt
foreach ($servers in $listofservers) {
Invoke-WmiMethod -class Win32_process -name Create -ArgumentList ("cmd.exe /c ipconfig /flushdns") -ComputerName $servers
Invoke-WmiMethod -class Win32_process -name Create -ArgumentList ("cmd.exe /c ipconfig /registerdns") -ComputerName $servers
}
By default you'll not get the output of the command, but you'll only get information if the command sucessfully ran on remote computer through this value :
ReturnValue
If this value equal to 0 that means the command was sucessfully executed on the remote server.
If you want to get the command output, you can achieve it but adding output redirection to txt file :
$listofservers = Get-Content .\servers.txt
foreach ($servers in $listofservers) {
Invoke-WmiMethod -class Win32_process -name Create -ArgumentList ("cmd.exe /c ipconfig /flushdns > c:\flushdnsresult.txt") -ComputerName $servers
Invoke-WmiMethod -class Win32_process -name Create -ArgumentList ("cmd.exe /c ipconfig /registerdns > c:\registerdnsresult.txt") -ComputerName $servers
}
Then you'll find a txt file on your remote server containing the result output of cmd command.
If you upgrade your powershell version from 2 (highly recommended - I have a powershell & dotnet update script to do this also) you can use:
# Get Windows servers on Domain
####################
$serversraw=Get-ADComputer -Filter {(OperatingSystem -like "*windows*")}
# Filter responsive
####################
$serversup = $serversraw.name | where {Test-Connection $_ -quiet -count 1}
# Flush DNS & reregister
####################
Clear-DnsClientCache -cimsession $serversup
Register-DnsClientCache -cimsession $serversup