Get installed software in VM with Invoke-VMScript

by Grzegorz Kulikowski
invoke-vmscript

This post is about using Invoke-VMScript, in order to get software list inside windows virtual machine guest . I am using Chris Dent’s script to obtain all the software from Windows and some tiny tricks we will make it happen. His script :

function Get-InstalledSoftware {
<#
.SYNOPSIS
Get all installed from the Uninstall keys in the registry.
.DESCRIPTION
Read a list of installed software from each Uninstall key.
This function provides an alternative to using Win32_Product.
.EXAMPLE
Get-InstalledSoftware
Get the list of installed applications from the local computer.
.EXAMPLE
Get-InstalledSoftware -IncludeLoadedUserHives
Get the list of installed applications from the local computer, including each loaded user hive.
.EXAMPLE
Get-InstalledSoftware -ComputerName None -DebugConnection
Display all error messages thrown when attempting to audit the specified computer.
.EXAMPLE
Get-InstalledSoftware -IncludeBlankNames
Display all results, including those with very limited information.
#>
[CmdletBinding()]
[OutputType([PSObject])]
param (
# The computer to execute against. By default, Get-InstalledSoftware reads registry keys on the local computer.
[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[String]$ComputerName = $env:COMPUTERNAME,
# Attempt to start the remote registry service if it is not already running. This parameter will only take effect if the service is not disabled.
[Switch]$StartRemoteRegistry,
# Some software packages, such as DropBox install into a users profile rather than into shared areas. Get-InstalledSoftware can increase the search to include each loaded user hive.
#
# If a registry hive is not loaded it cannot be searched, this is a limitation of this search style.
[Switch]$IncludeLoadedUserHives
)
begin {
$keys = @(
'Software\Microsoft\Windows\CurrentVersion\Uninstall'
'Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
)
}
process {
# If the remote registry service is stopped before this script runs it will be stopped again afterwards.
if ($StartRemoteRegistry) {
$shouldStop = $false
$service = Get-Service RemoteRegistry Computer $ComputerName
if ($service.Status -eq 'Stopped' -and $service.StartType -ne 'Disabled') {
$shouldStop = $true
$service | Start-Service
}
}
$baseKeys = [System.Collections.Generic.List[Microsoft.Win32.RegistryKey]]::new()
$baseKeys.Add([Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $ComputerName, 'Registry64'))
if ($IncludeLoadedUserHives) {
try {
$baseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', $ComputerName, 'Registry64')
foreach ($name in $baseKey.GetSubKeyNames()) {
if (-not $name.EndsWith('_Classes')) {
Write-Debug ('Opening {0}' -f $name)
try {
$baseKeys.Add($baseKey.OpenSubKey($name, $false))
} catch {
$errorRecord = [System.Management.Automation.ErrorRecord]::new(
$_.Exception.GetType()::new(
('Unable to access sub key {0} ({1})' -f $name, $_.Exception.InnerException.Message.Trim()),
$_.Exception
),
'SubkeyAccessError',
'InvalidOperation',
$name
)
Write-Error ErrorRecord $errorRecord
}
}
}
} catch [Exception] {
Write-Error ErrorRecord $_
}
}
foreach ($baseKey in $baseKeys) {
Write-Verbose ('Reading {0}' -f $baseKey.Name)
if ($basekey.Name -eq 'HKEY_LOCAL_MACHINE') {
$username = 'LocalMachine'
} else {
# Attempt to resolve a SID
try {
[System.Security.Principal.SecurityIdentifier]$sid = Split-Path $baseKey.Name Leaf
$username = $sid.Translate([System.Security.Principal.NTAccount]).Value
} catch {
$username = Split-Path $baseKey.Name Leaf
}
}
foreach ($key in $keys) {
try {
$uninstallKey = $baseKey.OpenSubKey($key, $false)
if ($uninstallKey) {
$is64Bit = $true
if ($key -match 'Wow6432Node') {
$is64Bit = $false
}
foreach ($name in $uninstallKey.GetSubKeyNames()) {
$packageKey = $uninstallKey.OpenSubKey($name)
$installDate = Get-Date
$dateString = $packageKey.GetValue('InstallDate')
if (-not $dateString -or -not [DateTime]::TryParseExact($dateString, 'yyyyMMdd', (Get-Culture), 'None', [Ref]$installDate)) {
$installDate = $null
}
[PSCustomObject]@{
Name = $name
DisplayName = $packageKey.GetValue('DisplayName')
DisplayVersion = $packageKey.GetValue('DisplayVersion')
InstallDate = $installDate
InstallLocation = $packageKey.GetValue('InstallLocation')
HelpLink = $packageKey.GetValue('HelpLink')
Publisher = $packageKey.GetValue('Publisher')
UninstallString = $packageKey.GetValue('UninstallString')
URLInfoAbout = $packageKey.GetValue('URLInfoAbout')
Is64Bit = $is64Bit
Hive = $baseKey.Name
Path = Join-Path Path $key ChildPath $name
Username = $username
ComputerName = $ComputerName
}
}
}
} catch {
Write-Error ErrorRecord $_
}
}
}
# Stop the remote registry service if required
if ($StartRemoteRegistry -and $shouldStop) {
$service | Stop-Service
}
}
}

Implementation with Invoke-VMScript

We will be using invoke-VMScript to call the script from inside the VM. What is also worth noticing is that we will be sending ourselves the json format as an output. The returned object contains property ScriptOutput, which be default is just text.

When the script is about to finish it converts the output to Json format so we can transform it easily from Json later on in our powershell session.

You will see inside the $o/$output ScriptOutput property showing what was on the screen.

ScriptOutput content from Invoke-VMScript
ScriptOutput content

At this moment this is Json string. Now we are converting it.

select-object from returned Json coming from Invoke-VMScript
select-object from returned Json

At this moment we can now filter , modify, select etc anything we want there.

Parsed Invoke-VMScript output as Json
Parsed output as Json

What is worth mentioning is that in order to run Invoke-VMScript you need to have the credentials to local account with administrator rights. You also might hit a problem where you need to adjust your InvalidCertificateAction using Set-PowerCLIConfiguration.

Mystery

Since i am already here writing about Invoke-VMScript, today i finally understood, why i could not find most of the times cmdlets in VMware {code} documentation portal. Have a look at this 😉

Invoke-VMScript search comparison on {code} portal
Invoke-VMScript search comparison on {code} portal

It’s because of the text is case sensitive i believe. As you can see it is all case sensitive, one must have great memory in order to remember the exact words. invoke-vmscript is not correct, but Invoke-VMScript is. If i do not know exactly what i am looking for, then why would one assume that i know how to type it correctly. Well, another mystery i guess. I have submitted my feedback already, maybe this will get fixed. Maybe its just me, doing the search not in compatible way. No idea to be honest.

Summary

Yesterday i wrote about about the trick with ConvertTo-Json and Invoke-VMScript, if you are interested about about this have a look there. Chris did a really great job with the pulling software script, i just modified it a bit to have it working with Invoke-VMScript. Information like this can be really useful with CMDB systems, or ticketing, helpdesk software.

You may also like

Leave a Reply

Chinese (Simplified)EnglishFrenchGermanHindiPolishSpanish
Streaming live on Twitch right now.
CURRENTLY OFFLINE