Page 1 of 2

PowerShell script to Get-RemotePrograms and export to .CSV for multiple AD computer object not working for bulk?

Posted: Thu Jan 10, 2019 4:20 pm
by ITEngineer
Hi All,

I need to export the .CSV for all computers with Microsoft Office installed, hence I've stumbled upon this script: https://gallery.technet.microsoft.com/G ... b4#content

When I run the script using one line of code at the end of the file, it can give me the result successfully:
Get-RemoteProgram -ComputerName 'MyLaptop01-NEW' -IncludeProgram ('*Office*') | Sort-Object ProgramName | Export-Csv -Path C:\Logs\Office.txt -NoTypeInformation
However, when I modify/enhance it using the below codes I pasted at the end of the Get-RemoteProgram.ps1 script, it throws out some errors:

Code: Select all

$OUList = @(
	"OU=Workstations,OU=Testing,DC=Domain,DC=com"
    "OU=Desktops,DC=Domain,DC=com"
	"OU=Laptops,DC=Domain,DC=com"
)

$OUList | ForEach-Object {
    $OU = $_

    $Computers = Get-ADComputer -Properties Name -Filter { Enabled -eq $True -and OperatingSystem -like "*Windows*" } -SearchBase $OU |
        Where-Object {Test-Connection $_.Name -Count 1 -Quiet} |
        Select-Object -ExpandProperty Name |
        Sort-Object

    ForEach ($Computer in $Computers) {
        Write-Host "Checking $Computer ..."
        Get-RemoteProgram -ComputerName $Computer -IncludeProgram ('*Office*') | Sort-Object ProgramName | Export-Csv -Path C:\Logs\Office.txt -NoTypeInformation
    }
}
it throws the error:
Checking WKS042... Get-RemoteProgram : Exception calling "OpenRemoteBaseKey" with "2" argument(s): "The network path was not found.
"
At line:258 char:9
+ Get-RemoteProgram -ComputerName $Computer -IncludeProgram ('* ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-RemoteProgram

Checking WKS021...
Get-RemoteProgram : Exception calling "OpenRemoteBaseKey" with "2" argument(s): "Attempted to perform an unauthorized operation."
At line:258 char:9
+ Get-RemoteProgram -ComputerName $Computer -IncludeProgram ('* ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-RemoteProgram

Any help to fix the code above is appreciated.

Thanks in advance.

Re: PowerShell script to Get-RemotePrograms and export to .CSV for multiple AD computer object not working for bulk?

Posted: Thu Jan 10, 2019 4:22 pm
by ITEngineer
Here's the full script I run:

Code: Select all

Function Get-RemoteProgram {
<#
.Synopsis
Generates a list of installed programs on a computer

.DESCRIPTION
This function generates a list by querying the registry and returning the installed programs of a local or remote computer.

.NOTES   
Name       : Get-RemoteProgram
Author     : Jaap Brasser
Version    : 1.4.1
DateCreated: 2013-08-23
DateUpdated: 2018-04-09
Blog       : http://www.jaapbrasser.com

.LINK
http://www.jaapbrasser.com

.PARAMETER ComputerName
The computer to which connectivity will be checked

.PARAMETER Property
Additional values to be loaded from the registry. Can contain a string or an array of string that will be attempted to retrieve from the registry for each program entry

.PARAMETER IncludeProgram
This will include the Programs matching that are specified as argument in this parameter. Wildcards are allowed. Both Include- and ExcludeProgram can be specified, where IncludeProgram will be matched first

.PARAMETER ExcludeProgram
This will exclude the Programs matching that are specified as argument in this parameter. Wildcards are allowed. Both Include- and ExcludeProgram can be specified, where IncludeProgram will be matched first

.PARAMETER ProgramRegExMatch
This parameter will change the default behaviour of IncludeProgram and ExcludeProgram from -like operator to -match operator. This allows for more complex matching if required.

.PARAMETER LastAccessTime
Estimates the last time the program was executed by looking in the installation folder, if it exists, and retrieves the most recent LastAccessTime attribute of any .exe in that folder. This increases execution time of this script as it requires (remotely) querying the file system to retrieve this information.

.PARAMETER ExcludeSimilar
This will filter out similar programnames, the default value is to filter on the first 3 words in a program name. If a program only consists of less words it is excluded and it will not be filtered. For example if you Visual Studio 2015 installed it will list all the components individually, using -ExcludeSimilar will only display the first entry.

.PARAMETER SimilarWord
This parameter only works when ExcludeSimilar is specified, it changes the default of first 3 words to any desired value.

.EXAMPLE
Get-RemoteProgram

Description:
Will generate a list of installed programs on local machine

.EXAMPLE
Get-RemoteProgram -ComputerName server01,server02

Description:
Will generate a list of installed programs on server01 and server02

.EXAMPLE
Get-RemoteProgram -ComputerName Server01 -Property DisplayVersion,VersionMajor

Description:
Will gather the list of programs from Server01 and attempts to retrieve the displayversion and versionmajor subkeys from the registry for each installed program

.EXAMPLE
'server01','server02' | Get-RemoteProgram -Property Uninstallstring

Description
Will retrieve the installed programs on server01/02 that are passed on to the function through the pipeline and also retrieves the uninstall string for each program

.EXAMPLE
'server01','server02' | Get-RemoteProgram -Property Uninstallstring -ExcludeSimilar -SimilarWord 4

Description
Will retrieve the installed programs on server01/02 that are passed on to the function through the pipeline and also retrieves the uninstall string for each program. Will only display a single entry of a program of which the first four words are identical.

.EXAMPLE
Get-RemoteProgram -Property installdate,uninstallstring,installlocation -LastAccessTime | Where-Object {$_.installlocation}

Description
Will gather the list of programs from Server01 and retrieves the InstallDate,UninstallString and InstallLocation properties. Then filters out all products that do not have a installlocation set and displays the LastAccessTime when it can be resolved.

.EXAMPLE
Get-RemoteProgram -Property installdate -IncludeProgram *office*

Description
Will retrieve the InstallDate of all components that match the wildcard pattern of *office*

.EXAMPLE
Get-RemoteProgram -Property installdate -IncludeProgram 'Microsoft Office Access','Microsoft SQL Server 2014'

Description
Will retrieve the InstallDate of all components that exactly match Microsoft Office Access & Microsoft SQL Server 2014

.EXAMPLE
Get-RemoteProgram -IncludeProgram ^Office -ProgramRegExMatch

Description
Will retrieve the InstallDate of all components that match the regex pattern of ^Office.*, which means any ProgramName starting with the word Office
#>
    [CmdletBinding(SupportsShouldProcess=$true)]
    param(
        [Parameter(ValueFromPipeline              =$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0
        )]
        [string[]]
            $ComputerName = $env:COMPUTERNAME,
        [Parameter(Position=0)]
        [string[]]
            $Property,
        [string[]]
            $IncludeProgram,
        [string[]]
            $ExcludeProgram,
        [switch]
            $ProgramRegExMatch,
        [switch]
            $LastAccessTime,
        [switch]
            $ExcludeSimilar,
        [int]
            $SimilarWord
    )

    begin {
        $RegistryLocation = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\',
                            'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\'

        if ($psversiontable.psversion.major -gt 2) {
            $HashProperty = [ordered]@{}    
        } else {
            $HashProperty = @{}
            $SelectProperty = @('ComputerName','ProgramName')
            if ($Property) {
                $SelectProperty += $Property
            }
            if ($LastAccessTime) {
                $SelectProperty += 'LastAccessTime'
            }
        }
    }

    process {
        foreach ($Computer in $ComputerName) {
            try {
                $socket = New-Object Net.Sockets.TcpClient($Computer, 445)
                if ($socket.Connected) {
                    $RegBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$Computer)
                    $RegistryLocation | ForEach-Object {
                        $CurrentReg = $_
                        if ($RegBase) {
                            $CurrentRegKey = $RegBase.OpenSubKey($CurrentReg)
                            if ($CurrentRegKey) {
                                $CurrentRegKey.GetSubKeyNames() | ForEach-Object {
                                    $HashProperty.ComputerName = $Computer
                                    $HashProperty.ProgramName = ($DisplayName = ($RegBase.OpenSubKey("$CurrentReg$_")).GetValue('DisplayName'))
                                    
                                    if ($IncludeProgram) {
                                        if ($ProgramRegExMatch) {
                                            $IncludeProgram | ForEach-Object {
                                                if ($DisplayName -notmatch $_) {
                                                    $DisplayName = $null
                                                }
                                            }
                                        } else {
                                            $IncludeProgram | ForEach-Object {
                                                if ($DisplayName -notlike $_) {
                                                    $DisplayName = $null
                                                }
                                            }
                                        }
                                    }

                                    if ($ExcludeProgram) {
                                        if ($ProgramRegExMatch) {
                                            $ExcludeProgram | ForEach-Object {
                                                if ($DisplayName -match $_) {
                                                    $DisplayName = $null
                                                }
                                            }
                                        } else {
                                            $ExcludeProgram | ForEach-Object {
                                                if ($DisplayName -like $_) {
                                                    $DisplayName = $null
                                                }
                                            }
                                        }
                                    }

                                    if ($DisplayName) {
                                        if ($Property) {
                                            foreach ($CurrentProperty in $Property) {
                                                $HashProperty.$CurrentProperty = ($RegBase.OpenSubKey("$CurrentReg$_")).GetValue($CurrentProperty)
                                            }
                                        }
                                        if ($LastAccessTime) {
                                            $InstallPath = ($RegBase.OpenSubKey("$CurrentReg$_")).GetValue('InstallLocation') -replace '\\$',''
                                            if ($InstallPath) {
                                                $WmiSplat = @{
                                                    ComputerName = $Computer
                                                    Query        = $("ASSOCIATORS OF {Win32_Directory.Name='$InstallPath'} Where ResultClass = CIM_DataFile")
                                                    ErrorAction  = 'SilentlyContinue'
                                                }
                                                $HashProperty.LastAccessTime = Get-WmiObject @WmiSplat |
                                                    Where-Object {$_.Extension -eq 'exe' -and $_.LastAccessed} |
                                                    Sort-Object -Property LastAccessed |
                                                    Select-Object -Last 1 | ForEach-Object {
                                                        $_.ConvertToDateTime($_.LastAccessed)
                                                    }
                                            } else {
                                                $HashProperty.LastAccessTime = $null
                                            }
                                        }
                                        
                                        if ($psversiontable.psversion.major -gt 2) {
                                            [pscustomobject]$HashProperty
                                        } else {
                                            New-Object -TypeName PSCustomObject -Property $HashProperty |
                                            Select-Object -Property $SelectProperty
                                        }
                                    }
                                    $socket.Close()
                                }

                            }

                        }

                    }
                }
            } catch {
                Write-Error $_
            }
        }
    }
}

$OUList = @(
	"OU=Workstations,OU=Testing,DC=Domain,DC=com"
    "OU=Desktops,DC=Domain,DC=com"
	"OU=Laptops,DC=Domain,DC=com"
)

$OUList | ForEach-Object {
    $OU = $_

    $Computers = Get-ADComputer -Properties Name -Filter { Enabled -eq $True -and OperatingSystem -like "*Windows*" } -SearchBase $OU |
        Where-Object {Test-Connection $_.Name -Count 1 -Quiet} |
        Select-Object -ExpandProperty Name |
        Sort-Object

    ForEach ($Computer in $Computers) {
        Write-Host "Checking $Computer ..."
        Get-RemoteProgram -ComputerName $Computer -IncludeProgram ('*Office*') | Sort-Object ProgramName | Export-Csv -Path C:\Logs\Office.txt -NoTypeInformation
    }
}

Re: PowerShell script to Get-RemotePrograms and export to .CSV for multiple AD computer object not working for bulk?

Posted: Thu Jan 10, 2019 4:50 pm
by jvierra
Look at the error message. It tells you exactly what the issue is:

"Attempted to perform an unauthorized operation."

Re: PowerShell script to Get-RemotePrograms and export to .CSV for multiple AD computer object not working for bulk?

Posted: Thu Jan 10, 2019 4:54 pm
by ITEngineer
jvierra wrote: Thu Jan 10, 2019 4:50 pm Look at the error message. It tells you exactly what the issue is:

"Attempted to perform an unauthorized operation."
What does it means?

But when I run the script line by line manually, it works. :shock:

The below lines is also working and returns the online PC that is ping-able:

Code: Select all

$OUList = @(
	"OU=Workstations,OU=Testing,DC=Domain,DC=com"
    "OU=Desktops,DC=Domain,DC=com"
	"OU=Laptops,DC=Domain,DC=com"
)

$OUList | ForEach-Object {
    $OU = $_

    $Computers = Get-ADComputer -Properties Name -Filter { Enabled -eq $True -and OperatingSystem -like "*Windows*" } -SearchBase $OU |
        Where-Object {Test-Connection $_.Name -Count 1 -Quiet} |
        Select-Object -ExpandProperty Name |
        Sort-Object

    ForEach ($Computer in $Computers) {
        Write-Host "Checking $Computer ..."
    }
}

Re: PowerShell script to Get-RemotePrograms and export to .CSV for multiple AD computer object not working for bulk?

Posted: Thu Jan 10, 2019 5:18 pm
by jvierra
You are trying to access a part of the registry that is forbidden. This is the full error line.

Get-RemoteProgram : Exception calling "OpenRemoteBaseKey" with "2" argument(s): "Attempted to perform an unauthorized

Re: PowerShell script to Get-RemotePrograms and export to .CSV for multiple AD computer object not working for bulk?

Posted: Thu Jan 10, 2019 5:37 pm
by ITEngineer
jvierra wrote: Thu Jan 10, 2019 5:18 pm You are trying to access a part of the registry that is forbidden. This is the full error line.

Get-RemoteProgram : Exception calling "OpenRemoteBaseKey" with "2" argument(s): "Attempted to perform an unauthorized
Is there anything that I need to do?
because I have already run the Visual Studio Code as Administrator and also my logon account is part of domain admins.

Re: PowerShell script to Get-RemotePrograms and export to .CSV for multiple AD computer object not working for bulk?

Posted: Thu Jan 10, 2019 5:49 pm
by jvierra
What VS code? This is a script. Run it in PowerShell.

I ran the code from the Gallery. It is a Jaap Brasser script. His scripts are very good always. It works fine for me. I suspect you changed the code somehow and now it is not working.

Have you run it locally? Just type the name of the function. If it has not been changed it will list all programs installed on the local machine.

Re: PowerShell script to Get-RemotePrograms and export to .CSV for multiple AD computer object not working for bulk?

Posted: Thu Jan 10, 2019 8:38 pm
by ITEngineer
jvierra wrote: Thu Jan 10, 2019 5:49 pm What VS code? This is a script. Run it in PowerShell.

I ran the code from the Gallery. It is a Jaap Brasser script. His scripts are very good always. It works fine for me. I suspect you changed the code somehow and now it is not working.

Have you run it locally? Just type the name of the function. If it has not been changed it will list all programs installed on the local machine.
OK, when I run it from PowerShell ISE, it is also not working as well.

The only modification is to insert the loop for multiple different remote Computer based on specific OU then export the result to .CSV.

The script works fine when I run it with single line of code of my own local PC.

Re: PowerShell script to Get-RemotePrograms and export to .CSV for multiple AD computer object not working for bulk?

Posted: Thu Jan 10, 2019 8:53 pm
by jvierra
An array declaration requires commas.

[$OUList = @(
'OU=Workstations,OU=Testing,DC=Domain,DC=com',
'OU=Desktops,DC=Domain,DC=com',
'OU=Laptops,DC=Domain,DC=com'
)

Re: PowerShell script to Get-RemotePrograms and export to .CSV for multiple AD computer object not working for bulk?

Posted: Thu Jan 10, 2019 9:04 pm
by jvierra
This would be easier to work with and debug.

Code: Select all

$OUList = @(
    'OU=Workstations,OU=Testing,DC=Domain,DC=com',
    'OU=Desktops,DC=Domain,DC=com',
    'OU=Laptops,DC=Domain,DC=com'
)

$computers = ForEach-Object{
    Get-ADComputer -Properties Name -Filter { Enabled -eq $True -and OperatingSystem -like "*Windows*" } -SearchBase $_ |
        Where-Object { Test-Connection $_.Name -Count 1 -Quiet }
}

$computers |
    ForEach-Object {
        Write-Host "Checking $($_.Name) ..."
        Get-RemoteProgram -ComputerName $_.Name -IncludeProgram '*Office*'
    } |
    Sort-Object ProgramName |
    Export-Csv -Path C:\Logs\Office.txt -NoTypeInformation