Create a hashtable with the results of a foreach over a hashtable

This topic contains 10 replies, has 2 voices, and was last updated by Profile photo of Dave Wyatt Dave Wyatt 2 years, 4 months ago.

  • Author
    Posts
  • #17916
    Profile photo of Terry McKenna
    Terry McKenna
    Participant

    Hi Guys,

    I am trying to write a function that will be used in a HTML report. The functions purpose is to audit specific applications based on a finite list of application GUIDs. I am querying the WIndows Uninstall registry hive and if the GUID test true then I report it.

    The HTML report template simply creates a new object and assigns the properties of a hashtable then uses that object to generate the report output. The problem I am running into is that I need to create said hashtable, so I can create said object, based on the results of a foreach loop run over the existing hashtable of application GUIDs.

    I've read Don Jones post on TechNet about creating custom objects and I've read numerous other articles about hashtables, arrays, looping, etc. I'm not having any luck.

    function Get-InstalledApps {
    
    # Create a PSDrive for the root Uninstall Key
    If (-NOT(Get-ChildItem Uninstall_Key -ErrorAction SilentlyContinue)) {
        New-PSDrive -Name Uninstall_Key -PSProvider Registry -Root "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
        }
        
    # Hashtable of Application GUIDs Added Silverlight so this will resolve on most computers for testing
    $appGUIDs = @{
    '{ABCD-8133-4AFA-AD22-E058D091234}' = 'myApp v9 Update 5 (9.0.5)';
    '{EFGH-C9CE-4FA5-BA0A-3BA26D95678}' = 'myApp v9 Update 4 (9.0.4)';
    '{IJKL-6634-4D7E-868B-0686CC89101}' = 'myApp v9 Update 3 (9.0.3)';
    '{MNOP-5A62-4972-94FE-779580BE121}' = 'myApp v9 Update 2 (9.0.2)';
    '{QRST-FA5F-45E4-B98C-B51ACA32132}' = 'myApp v9 Update 1 (9.0.1)';
    '{89F4137D-6C26-4A84-BDB8-2E5A4BB71E00}' = 'Microsoft Silverlight'}
    
    foreach ($k in $appGUIDs.Keys) {
            if (Test-Path Uninstall_Key:\$k) 
        {  
            $installedApps += $appGUIDs.Get_Item($k)
        }
      }
    }
    

    So at this point if I write the contents of the $installedApps variable to the console I get a list of the currently installed apps. What I need to do next is create a new hashtable that the New-Object command in the HTML report template can use as its properties list. Haven't been able to figure that one out.

    Thanks in advance.

    Terry

  • #17920
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    What do you want the resulting table to look like? I'm not sure what you're trying to accomplish, based on your description so far.

  • #17921
    Profile photo of Terry McKenna
    Terry McKenna
    Participant

    I've attached the basic report script from Don Jones's HTML Reports in PowerShell eBook.

    I want to add a section to the report that audits the computer for known applications GUIDs. A simple list of the apps is fine.

    Thanks

  • #17923
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    If you want a single-column table with a header of something like "InstalledAppName", then you just need to create objects which have a property by that name before piping it to one of the ConvertTo commands. In this case, you'd change this line:

    $installedApps += $appGUIDs.Get_Item($k)
    

    To this:

    $installedApps += New-Object psobject -Property @{ InstalledAppName = $appGUIDs[$k] }
    

    (On a side note, I replaced the explicit call to Get_Item with the indexer syntax as well. I can't think of any situation, off the top of my head, where you have to explicitly use a get_ or set_ method; those map to either indexers (if it's get_Item) or properties (if it's get_AnythingElse).

  • #17924
    Profile photo of Terry McKenna
    Terry McKenna
    Participant

    Dave Wyatt wrote:If you want a single-column table with a header of something like "InstalledAppName", then you just need to create objects which have a property by that name before piping it to one of the ConvertTo commands. In this case, you'd change this line:

    $installedApps += $appGUIDs.Get_Item($k)

    To this:

    $installedApps += New-Object psobject -Property @{ InstalledAppName = $appGUIDs[$k] }

    (On a side note, I replaced the explicit call to Get_Item with the indexer syntax as well. I can't think of any situation, off the top of my head, where you have to explicitly use a get_ or set_ method; those map to either indexers (if it's get_Item) or properties (if it's get_AnythingElse).

    I get an error

    Method invocation failed because [System.Management.Automation.PSObject] doesn't contain a method named 'op_Addition'.
    At C:\Users\mckenna\PowerShell\Get-InstalledApps.ps1:25 char:26
    +         $installedApps += < <<<  New-Object -TypeName PSObject -Property @{ InstalledAppName = $appGUIDs[$k] }
        + CategoryInfo          : InvalidOperation: (op_Addition:String) [], RuntimeException
        + FullyQualifiedErrorId : MethodNotFound
    

    A property named "InstalledAppName" is created for $installedApps and has the value of one of the installed applications. Should your suggestion facilitate iteration through the hashtable; if that makes any sense?

    Thanks

  • #17925
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Ah, right; you hadn't initialized $installedApps as an empty array first, which is what people generally do when they use this += syntax. Here's the way I would prefer to do that:

    $installedApps = @(
        foreach ($k in $appGUIDs.Keys)
        {
            if (Test-Path Uninstall_Key:\$k) 
            {  
                New-Object psobject -Property @{ InstalledAppName = $appGUIDs[$k] }
            }
        }
    )
    

    This scales better; the += operator on arrays gets very slow if you're working with large data sets. In your small example, it makes no difference, but it's a habit I like to keep anyway. 🙂

    To use the += operator, it would look like this:

    $installedApps = @()
    
    foreach ($k in $appGUIDs.Keys)
    {
        if (Test-Path Uninstall_Key:\$k) 
        {  
            $installedApps += New-Object psobject -Property @{ InstalledAppName = $appGUIDs[$k] }
        }
    }
    
  • #17929
    Profile photo of Terry McKenna
    Terry McKenna
    Participant

    This doesn't seem to be creating the $installedApps object. When I drop the function into the HTML template only the header from $html_ia is displayed and if I try a Get-Member on $installedApps I get an error that the object does not exist.

    function Get-InstalledApps {
     [CmdletBinding()]
        param(
            [Parameter(Mandatory=$True)][string]$ComputerName
        )
    # Path to root Windows Uninstall registry hive
    $uninstallKey =  "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
     
    # Hashtable of Application GUIDs Added Silverlight so this will resolve on most computers for testing
    $appGUIDs = @{
    '{ABCD-8133-4AFA-AD22-E058D091234}' = 'myApp v9 Update 5 (9.0.5)';
    '{EFGH-C9CE-4FA5-BA0A-3BA26D95678}' = 'myApp v9 Update 4 (9.0.4)';
    '{IJKL-6634-4D7E-868B-0686CC89101}' = 'myApp v9 Update 3 (9.0.3)';
    '{MNOP-5A62-4972-94FE-779580BE121}' = 'myApp v9 Update 2 (9.0.2)';
    '{QRST-FA5F-45E4-B98C-B51ACA32132}' = 'myApp v9 Update 1 (9.0.1)';
    '{89F4137D-6C26-4A84-BDB8-2E5A4BB71E00}' = 'Microsoft Silverlight';
    '{90150000-002A-0409-1000-0000000FF1CE}' = 'Microsoft Office 2013'}
    
    $installedApps = @(
        foreach ($k in $appGUIDs.Keys)
        {
            if (Test-Path "$uninstallKey\$k")
            {
             New-Object -TypeName PSObject -Property @{ InstalledApps = $appGUIDs[$k] }
            }
        }
    )
    
    }
    .
    .
    .
    .   
        if ($everything_ok) {
        }
    }
    
    if ($everything_ok) {
        $filepath = Join-Path -Path $Path -ChildPath "$computer.html"
        $html_os = Get-InfoOS -ComputerName $computer |
            ConvertTo-HTML -As List -Fragment -PreContent "OS" |
            Out-String
        $html_cs = Get-InfoCompSystem -ComputerName $computer |
            ConvertTo-HTML -As List -Fragment -PreContent "Hardware" |
            Out-String
        $html_pr = Get-InfoProc -ComputerName $computer |
            ConvertTo-HTML -Fragment -PreContent "Processes" |
            Out-String
        $html_sv = Get-InfoBadService -ComputerName $computer |
            ConvertTo-HTML -Fragment -PreContent "Check Services" |
            Out-String
        $html_na = Get-InfoNIC -ComputerName $Computer |
            ConvertTo-HTML -Fragment -PreContent "NICs" |
            Out-String
        $html_ia = Get-InstalledApps -ComputerName $Computer |
            ConvertTo-HTML -Fragment -PreContent "Installed Applications" |
            Out-String
        $params = @{'Title'="Report for $computer";
            'PreContent'="System Report for $computer";
            'PostContent'=$html_os,$html_cs,$html_ia,$html_pr,$html_sv,$html_na}
        ConvertTo-HTML @params | Out-File -FilePath $filepath
    }
    }
    
    

    Thanks

  • #17930
    Profile photo of Terry McKenna
    Terry McKenna
    Participant

    Here is an example of a different function in the template. Note how he creates a variable then does a foreach to create his hashtable, and lastly creates the object using the hashtable. I am trying to use this same methodology.

    function Get-InfoProc {
        [CmdletBinding()]
        param(
            [Parameter(Mandatory=$True)][string]$ComputerName
        )
        $procs = Get-WmiObject -class Win32_Process -ComputerName $ComputerName
        foreach ($proc in $procs) {
                 $props = @{'ProcName'=$proc.name;
                            'Executable'=$proc.ExecutablePath}
                 New-Object -TypeName PSObject -Property $props
        }
    }
    
  • #17931
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Your Get-InstalledApps function isn't outputting anything. In this case, you don't need to save it to an $installedApps variable inside the function at all; just let each object output to the pipeline as it's created:

    function Get-InstalledApps
    {
        [CmdletBinding()]
        param(
            [Parameter(Mandatory=$True)][string]$ComputerName
        )
        
        # Path to root Windows Uninstall registry hive
        $uninstallKey =  "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
     
        # Hashtable of Application GUIDs Added Silverlight so this will resolve on most computers for testing
        $appGUIDs = @{
            '{ABCD-8133-4AFA-AD22-E058D091234}' = 'myApp v9 Update 5 (9.0.5)';
            '{EFGH-C9CE-4FA5-BA0A-3BA26D95678}' = 'myApp v9 Update 4 (9.0.4)';
            '{IJKL-6634-4D7E-868B-0686CC89101}' = 'myApp v9 Update 3 (9.0.3)';
            '{MNOP-5A62-4972-94FE-779580BE121}' = 'myApp v9 Update 2 (9.0.2)';
            '{QRST-FA5F-45E4-B98C-B51ACA32132}' = 'myApp v9 Update 1 (9.0.1)';
            '{89F4137D-6C26-4A84-BDB8-2E5A4BB71E00}' = 'Microsoft Silverlight';
            '{90150000-002A-0409-1000-0000000FF1CE}' = 'Microsoft Office 2013'
        }
     
        foreach ($k in $appGUIDs.Keys)
        {
            if (Test-Path "$uninstallKey\$k")
            {
                New-Object -TypeName PSObject -Property @{ InstalledApps = $appGUIDs[$k] }
            }
        } 
    }
    
  • #17932
    Profile photo of Terry McKenna
    Terry McKenna
    Participant

    That seems to have gotten me back on track. Thank you for your persistence!

    Don't suppose you would know why I am getting an * in the output? See attached.

  • #17936
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    I have no idea... if you're using the version of Get-InstalledApps from my most recent post, I don't see any code that could be outputting that asterisk line. What does the code you're running look like at the moment?

You must be logged in to reply to this topic.