Automating vmrun with PowerShell - powershell

I have a PowerShell 2.0 script that I use to clone a target vmware workstation guest using vmrun in a windows host environment.
The code to clone a virtual machine executes correctly.
I am now trying to expand on this script to automate more of the process, for example, to check to see if a virtual machine is running, vmrun list, and to stop the virtual machine, vmrun stop [pathToVMXfile] if running.
With a windows command prompt, when I run vmrun list, it returns the following:
Total running VMs: 2
D:\[path]\[name].vmx
E:\[path]\[name].vmx
When I attempt the following in PowerShell, nothing returns. Here is what I have attempted so far. I am expecting an array to return with the values I see when I run from a command prompt.
$vmwareRun = "C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe"
#Original test, also tried with single quotes and no quotes,
#also tried quoting the -filePath
Start-Process $vmwareRun -ArgumentList "list"
#This gives a handle to the process but no return value
$myProcess = Start-Process $vmwareRun -ArgumentList "list" -PassThru
#$t is empty
Start-Process $vmwareRun -ArgumentList "list" -OutVariable $t
#$t is empty, clone command requires the -T ws parameters
Start-Process $vmwareRun -ArgumentList "-T ws list" -OutVariable $t
#RedirectStandardOutput creates a file with the expected output, $t is empty
Start-Process -FilePath "$vmwareRun" -ArgumentList $argList -OutVariable $t -RedirectStandardError "C:\testError.log" -RedirectStandardOutput C:\testOut.log"
No matter what I attempt, I do not get any output. What am I missing?
Note: Vmrun command line documentation is found here: "https://www.vmware.com/support/developer/vix-api/vix112_vmrun_command.pdf"
Thanks.

I found a similar post here: Capturing standard out and error with Start-Process
For whatever reason the powershell solution doesn't send results of the process to the -OutVariable as I expected.
So instead I followed the steps on the page indicated to create a new $pinfo = New-Object System.Diagnostics.ProcessStartInfo.
I set the corresponding properties and then started the process using System.Diagnostics.Process and it allows me to output the results to a string variable.

Related

Unable to run the parametrized batch file on remote machine using PowerShell

Execute the remote server parametrized batch file from PowerShell.
Doesn't throw an error nor executed command on remote machine.
$path = "D:\run\test-5.2.bat";
Invoke-Command -ComputerName testserver -Scriptblock { "$path" }
Script inside the bat file is msiexec with parameters, which shall execute through Command Prompt only.
Based on this msdn link, you can run a ps1 script file on remote computers. So if it is possible to "port" the content of the bat file in a ps1 it should work. Here is the msdn example:
Example 11: Run a script on all the computers listed in a text file
PS C:\> Invoke-Command -ComputerName (Get-Content Servers.txt) -FilePath C:\Scripts\Sample.ps1 -ArgumentList Process, Service
This example uses the Invoke-Command cmdlet to run the Sample.ps1 script on all of the computers listed in the Servers.txt file. The command uses the FilePath parameter to specify the script file. This command lets you run the script on the remote computers, even if the script file is not accessible to the remote computers.
When you submit the command, the content of the Sample.ps1 file is copied into a script block and the script block is run on each of the remote computers. This procedure is equivalent to using the ScriptBlock parameter to submit the contents of the script.
Hope that helps
$path is a string. PowerShell simply echoes bare strings instead of executing them, unlike CMD or bash. Use the call operator (&):
& "$path"
or Start-Process:
Start-Process cmd.exe -ArgumentList '/c', $path -NoNewWindow -Wait
to have PowerShell execute a string as a command. Since you say you're running msiexec.exe from the batch script using the latter may be required.
On top of that you have a scope issue. The variable $path inside the scriptblock is not the same as the one in the global scope. You can mitigate that via the using: scope qualifier:
Invoke-Command -Computer testserver -Scriptblock { & "$using:path" }
or by passing $path as an argument to the scriptblock:
Invoke-Command -Computer testserver -Scriptblock { & "$($args[0])" } -ArgumentList $path

Passing arguments as a variable when installing an MSI using Start-Process

I am new to Powershell and, of course, trying to learn on the fly for a project- No pressure, right! :-)
I am working on a script to run an MSI package in quiet mode, passing it an activation code as an argument, that I have to extract from an XML file.
So far, I have everything working except for getting Start-Process to run the MSI with the arguments being passed in a Variable.
Set-ExecutionPolicy Bypass -Force
[System.Xml.XmlDocument]$XML_Doc = new-object System.Xml.XmlDocument
$XML_Doc.load('c:\myfolder\Configinfo.XML')
$ActivationID = $XML_Doc.CONFIGINFO.SITEINFO.ACTIVATEID
write-host "Activation Id is: $ActivationID"
$InstallString = "`'/I C:\myfolder\myinstaller.msi akey="+'"'+$ActivationID+'"'''
#$InstallString = "`'/I C:\myfolder\myinstaller.msi akey=`"$($ActivationID)`"'"
write-host "$InstallString"'''
Start-Process msiexec.exe -ArgumentList $InstallString -Wait -NoNewWindow
#Start-Process msiexec.exe -ArgumentList '/I C:\myfolder\myinstaller.msi akey="12345678-abcd-1a1b-x9x1-a1b2c3d4e5f6"' -Wait -NoNewWindow
Above is the code I am working with now. The last line that is commented out is an activation string that works.
I have verified that $ActivationID is pulling back the correct value, and that $InstallString mirrors the argument list in the commented version of the Start-Process string.
Any help would be appreciated!
The Start-Process commands aren't necessary. PowerShell is a shell. It can run commands. Just put the commands you want to run directly in the script.
msiexec /i "C:\myfolder\myinstaller.msi" "AKEY=$ActivationID"
I quoted the parameters to msiexec.exe in case any of them contain spaces. PowerShell will automatically expand the $ActivationID variable into the string inside the double quotes.
Your ArgumentList is being passed incorrectly.
[Xml]$XML_Doc = Get-Content -Path 'C:\myfolder\Configinfo.xml'
$ActivationID = $XML_Doc.CONFIGINFO.SITEINFO.ACTIVATEID
Write-Host "Activation Id is: $ActivationID"
$Path = 'msiexec'
$ArgList = #('/i','"C:\path\file.msi"',"akey=`"$ActivationID`"")
Write-Host "$Path $ArgList"
Start-Process -FilePath $Path -ArgumentList $ArgList -Wait -NoNewWindow
First off, let me welcome you to Powershell! It's a great language and a great community gathered around a common cause.
Since you're new to the language, you can still learn new tricks and that's a good thing, because it's generally accepted that the Write-Host cmdlet is nearly always a poor choice. If you don't trust me, you should trust the inventor of Powershell.
Now that that's out of the way, we should look at your MSI command. With Powershell, we don't have to directly open msiexec, and we can call the MSI directly. I would break the path to the installer into its own variable, and then we can add all of our arguments on top of it. Also, don't forget the "/qn" switch which will actually make all of this silent. All in all, your new script will look something like this:
[System.Xml.XmlDocument]$XML_Doc = new-object System.Xml.XmlDocument
$XML_Doc.load('c:\myfolder\Configinfo.XML')
$ActivationID = $XML_Doc.CONFIGINFO.SITEINFO.ACTIVATEID
Write-Verbose "Activation Id is: $ActivationID"
$msipath = "C:\myfolder\myinstaller.msi"
$args = #("akey=$ActivationID", "/qn")
Write-Verbose "Install path is $msipath"
Write-Verbose "Activation key is $akey"
Start-Process $msipath -ArgumentList $args -Wait -NoNewWindow

Calling another executable and capturing output

Using a PowerShell script I'm trying to execute SqlPackage.exe to update a database. The problem is that it spawns a new CMD window and I can't capture the output effectively.
Here is my script:
&$SqlPackage "/Action:Script", "/SourceFile:$($Environment.PackageFile)", "/Profile:$($Environment.PublishProfile)", "/OutputPath:$($Environment.ScriptFile)";
where SqlPackage = "SQLPackage\SqlPackage.exe";.
Strangely when I execute the script on a Windows 2008 R2 web server the output is inline with the PowerShell output, which is ideally what I want. On my Windows 7 machine it spawns a new CMD window.
How can I route the output to the PowerShell window all the time, or at least pipe to another file?
Edit
Full solution with waiting for process to finish:
$p = Start-Process $SqlPackage -ArgumentList #("/Action:Script", "/SourceFile:$($Environment.PackageFile)", "/Profile:$($Environment.PublishProfile)", "/OutputPath:$($Environment.ScriptFile)") -NoNewWindow -Wait -Passthru
$p.WaitForExit()
You should be able to get the result you are looking for if you use Start-Process with the -NoNewWindow parameter:
Start-Process $SqlPackage -ArgumentList #("/Action:Script", "/SourceFile:$($Environment.PackageFile)", "/Profile:$($Environment.PublishProfile)", "/OutputPath:$($Environment.ScriptFile)") -NoNewWindow
If you need to direct the output to a file you can also use the -RedirectStandardOutput / -RedirectStandardError parameters of Start-Process

Invoke-Expression Not Running Command

I am pulling in a text file that was previously created that simply lists the KB numbers for all currently installed Windows Updates. I am trying to pull this file in and call wusa.exe and pass it the KB number to perform an uninstallation.
$a = Get-Content c:\hotfixid.txt
foreach ($kb in $a) {
$command = 'cmd /c wusa.exe /uninstall /kb:' + $kb.Trim().Substring(2) + ' /quiet /norestart'
#Write-Host $command
Write-Host "Currently uninstalling $kb"
Invoke-Expression -Command:$command
}
If I use
Write-Host $command
and copy that directly to a run dialog box in Windows, it completes successfully.
What happens when I run it in a PowerShell script is that it only outputs the Write-Host portions one after the other in about 2 seconds. I do not see any command windows opening and I don't see it actually DOING anything. I am running the PowerShell script 'As Administrator' with an unrestricted execution policy. I have also tried adding 'runas' to the $command to call the CMD window each time with administrative privileges and it made no difference. Calling it via Invoke-Command as well makes no difference.
PowerShell can run most commands directly w/o too much trouble.
Using Invoke-Expression just complicates matters, as does Invoke-Command or Start-Process, because you need to get the quoting right, and pass the arguments in a slightly unnatural way.
You can even skip running cmd.exe most of the time.
Try the following:
wusa.exe /uninstall "/kb:$($kb.Trim().Substring(2))" /quiet /norestart

PowerShell - Start-Process and Cmdline Switches

I can run this fine:
$msbuild = "C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe"
start-process $msbuild -wait
But when I run this code (below) I get an error:
$msbuild = "C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe /v:q /nologo"
start-process $msbuild -wait
Is there a way I can pass parameters to MSBuild using start-process? I'm open to not using start-process, the only reason I used it was I needed to have the "command" as a variable.
When I have
C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe /v:q /nologo
on a line by itself, how does that get handled in Powershell?
Should I be using some kind of eval() kind of function instead?
you are going to want to separate your arguments into separate parameter
$msbuild = "C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe"
$arguments = "/v:q /nologo"
start-process $msbuild $arguments
Using explicit parameters, it would be:
$msbuild = 'C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe'
start-Process -FilePath $msbuild -ArgumentList '/v:q','/nologo'
EDIT: quotes.
Warning
If you run PowerShell from a cmd.exe window created by Powershell, the 2nd instance no longer waits for jobs to complete.
cmd> PowerShell
PS> Start-Process cmd.exe -Wait
Now from the new cmd window, run PowerShell again and within it start a 2nd cmd window:
cmd2> PowerShell
PS> Start-Process cmd.exe -Wait
PS>
The 2nd instance of PowerShell no longer honors the -Wait request and ALL background process/jobs return 'Completed' status even thou they are still running !
I discovered this when my C# Explorer program is used to open a cmd.exe window and PS is run from that window, it also ignores the -Wait request.
It appears that any PowerShell which is a 'win32 job' of cmd.exe fails to honor the wait request.
I ran into this with PowerShell version 3.0 on windows 7/x64
I've found using cmd works well as an alternative, especially when you need to pipe the output from the called application (espeically when it doesn't have built in logging, unlike msbuild)
cmd /C "$msbuild $args" >> $outputfile
Unless the OP is using PowerShell Community Extensions which does provide a Start-Process cmdlet along with a bunch of others. If this the case then Glennular's solution works a treat since it matches the positional parameters of pscx\start-process : -path (position 1) -arguments (positon 2).