locked
Script which uses info from properties of get-hotfix, serveral other commands and is exporting the output in table format RRS feed

  • Question

  • Hello Everyone,

    I have an assignment to write a script which collects information about the current patches installed on systems, are there any pending reboots and when the systems were last rebooted.

    The choke point so far is that when I export the info from my custom object I get empty line for Server Name

     Name = $Patch.Source | Out-String

    I've tried serveral options, including to call back the $Computer variable but without success.

    Any idea what I might be missing ?

    Or any better solution for my task?

    NB: The info should be exported in CSV and reviewed in MS Excel.

    So far I have written this piece of code:

    #Enter Name of the Group file
    $Group = "test"
    
    $Computers = import-csv "C:\Users\User\Desktop\$Group.csv"
    
    $TestReboot = {
    $pendingRebootKey = "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired"
    $results = (Get-Item $pendingRebootKey -ErrorAction SilentlyContinue).Property
    if($results){ 
    Write-Output "There are pending reboots"
    }
    else
    {
    Write-Output "No pending reboots"
    }
    }
    
    $LastReboot = {
    
    Get-WmiObject win32_operatingsystem | select csname, @{LABEL=’LastBootUpTime’;EXPRESSION={$_.ConverttoDateTime($_.lastbootuptime)}} | format-list -property LastBootUpTime
    
    }
    
    #For each script block
    
    foreach ($Computer in $Computers)
    {
        $Computer=$Computer.Name
        $Patch = Invoke-Command -ComputerName $Computer -ScriptBlock {Get-HotFix}
        $RebootRequired = Invoke-Command -ComputerName $Computer -ScriptBlock $TestReboot
        $WhenReboot = Invoke-Command -ComputerName $Computer -ScriptBlock $LastReboot 
        $PatchInfo = [PSCustomObject]@{
        Name = $Patch.Source | Out-String
        Description = $Patch.Description | Out-String
        HoTFixID = $Patch.HotFixID | Out-String
        InstalledON = $Patch.InstalledOn| Out-String
        RebootRequired = $RebootRequired | Out-String
        LastReboot = $WhenReboot | Out-String
        }
    Export-csv -InputObject $PatchInfo -Path "C:\Users\User\Desktop\result_$Group.csv" -NoTypeInformation -Append
    }




    • Edited by Kushagi Monday, December 9, 2019 5:46 PM
    Monday, December 9, 2019 5:42 PM

Answers

  • This is how we code in PowerShell.

    $sb = {
        [pscustomobject]@{
            ComputerName = $env:COMPUTERNAME
            Patches = Get-HotFix
            RebootRequired = [bool](Get-Item 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update').RebootRequired
            LastReboot = (Get-CimInstance win32_operatingsystem).LastBootUpTime
            
        }
    }
    
    $results = import-csv "C:\Users\User\Desktop\$group.csv" |
        ForEach-Object{
           Invoke-Command -ComputerName $_.Name -ScriptBlock $sb
    }
    $results
    # format results for output to a CSV

    It is clear that the original code is a lot of things pasted together with no attempt to understand what is happening or what has to happen.

    Rich's issue with patches still needs to be resolved for output but collecting all of the data locally is easy once you understand how PowerShell is designed to work.

    You may ask specific questions about why this works and why most of the original code is unnecessary.

    You can execute the scriptblock locally to see how it works.

    $sb.Invoke() | fl *


    \_(ツ)_/




    • Edited by jrv Monday, December 9, 2019 10:04 PM
    • Marked as answer by Kushagi Tuesday, December 10, 2019 9:42 AM
    Monday, December 9, 2019 9:57 PM
  • Hello,

    So I tested the proposed code and it works like a charm !
    I've just slightly modified it to meet my needs, although it is not optimized in performance.

    Here is what I did: 

    $Group = "test"
    
    $sb = {
    
        for ($i=0; $i-lt (get-hotfix).Count;$i++)
        {
        [pscustomobject]@{
            ComputerName = $env:COMPUTERNAME
            PatchesKB = (Get-HotFix).HotFixID[$i]
            Description = (Get-HotFix).Description[$i]
            InstalledOn = (Get-HotFix).InstalledOn[$i]
            RebootRequired = [bool](Get-Item 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' -ErrorAction SilentlyContinue)
            LastReboot = (Get-CimInstance win32_operatingsystem).LastBootUpTime 
             }
        }
    }
    
        import-csv "C:\Users\User\Desktop\$group.csv" |
        ForEach-Object{
        Invoke-Command -ComputerName $PSitem.Name -ScriptBlock $sb | select -Property * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName | Export-csv -Path "C:\Users\User\Desktop\result_$Group.csv" -NoTypeInformation -Append
    }

    Added a for loop so I could get every element in a separate row of the CSV file.
    Otherwise I get everything on the same box after I open the file in Excel.

    Another thing is I added 

    select -Property * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName

    to a pipeline so I get rid of unnecessary properties.

    Many thanks to all who stepped in to help and special KUDOS to jrv.

    P.S.: Thank you for pointing out  Windows-PowerShell TFM 4 it is an amazing book.





    • Marked as answer by Kushagi Tuesday, December 10, 2019 9:43 AM
    • Edited by Kushagi Tuesday, December 10, 2019 3:12 PM
    Tuesday, December 10, 2019 9:30 AM

All replies

  • The Get-Hotfix cmdlet may be returning more than one object. In that case, using "$Patch" will be an array of objects, not the single hotfix you think it is.

    Also, are you sure that there's a property named "Source" in the object(s) returned by the Get-Hotfix cmdlet??? What about "InstalledOn"????


    --- Rich Matheisen MCSE&I, Exchange Ex-MVP (16 years)

    • Proposed as answer by jrv Monday, December 9, 2019 9:39 PM
    Monday, December 9, 2019 8:55 PM
  • This is how we code in PowerShell.

    $sb = {
        [pscustomobject]@{
            ComputerName = $env:COMPUTERNAME
            Patches = Get-HotFix
            RebootRequired = [bool](Get-Item 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update').RebootRequired
            LastReboot = (Get-CimInstance win32_operatingsystem).LastBootUpTime
            
        }
    }
    
    $results = import-csv "C:\Users\User\Desktop\$group.csv" |
        ForEach-Object{
           Invoke-Command -ComputerName $_.Name -ScriptBlock $sb
    }
    $results
    # format results for output to a CSV

    It is clear that the original code is a lot of things pasted together with no attempt to understand what is happening or what has to happen.

    Rich's issue with patches still needs to be resolved for output but collecting all of the data locally is easy once you understand how PowerShell is designed to work.

    You may ask specific questions about why this works and why most of the original code is unnecessary.

    You can execute the scriptblock locally to see how it works.

    $sb.Invoke() | fl *


    \_(ツ)_/




    • Edited by jrv Monday, December 9, 2019 10:04 PM
    • Marked as answer by Kushagi Tuesday, December 10, 2019 9:42 AM
    Monday, December 9, 2019 9:57 PM
  • Hello Rich,

    Thanks for stepping in.

    The script as you see it works for every property(that includes InstalledOn) except for the name.
    Now the source property as you mention it, could be returned by the invoke-command, because I execute it on remote computers.
    However there is "PSComputerName" for sure, but it doesnt work as for the rest of the Custom Object.
    For the array part I've converted all of the members of the object to a String, so I get all othe patch KB IDs and when they are installed as text - this is all exported in the CSV.

    Monday, December 9, 2019 10:01 PM
  • You can also simply do the following:

    $sb.Invoke().Patches

    Or do the same with the $results.

    The cliffhanger is that last reboot and reboot required are not related to patches so you will need a custom report that can format this in a data in a useful and meaningful way.


    \_(ツ)_/

    Monday, December 9, 2019 10:11 PM
  • Hello Rich,

    Thanks for stepping in.

    The script as you see it works for every property(that includes InstalledOn) except for the name.
    Now the source property as you mention it, could be returned by the invoke-command, because I execute it on remote computers.
    However there is "PSComputerName" for sure, but it doesnt work as for the rest of the Custom Object.
    For the array part I've converted all of the members of the object to a String, so I get all othe patch KB IDs and when they are installed as text - this is all exported in the CSV.

    Use "Select-Object" to exclude properties you don't want. You still have to understand PowerShell before understanding how this needs to be done.


    \_(ツ)_/

    Monday, December 9, 2019 10:30 PM
  • Hello jrv,

    I am still novice at scripting and surely I don't have the routine to do a quality code.
    However I am glad that I wrote to this forum board, so I could learn :)
    As you noticed the code I wrote, espesially the LastReboot and the PendingReboot is found on the internet.
    At the time it made sence to me when I copied it and tried to implement it in a bigger script.
    Now as you pasted your version it seems smoother and cleaner way to do the report.

    Once I get my hands on my work computer I will test and share the results.
    Thank you for the time spend to help a colleague !

    P.S.: In the previous versions of the 'script' I used Select-Obeject, I don't know why I decided to remove it, but note taken.

    Best Regards !


    • Edited by Kushagi Monday, December 9, 2019 11:22 PM
    Monday, December 9, 2019 10:49 PM
  • I strongly recommend that you learn basic PowerShell before continuing. It will save you a lot of time and confusion.

    Free top rated book: Windows-PowerShell TFM 4 


    \_(ツ)_/

    Monday, December 9, 2019 11:22 PM
  • I do see a "Source" property when I call get-hotfix locally.  Not sure about a remote computer...

    One of the issues here is that get-hotfix returns an array of objects, as mentioned.  So you need to loop through each hotfix and create (and combine) each object to create your own custom array of custom objects.

    I'm sure there's other examples, but you can search yourself:

    https://www.andreasbijl.com/powershell-create-collections-of-custom-objects/

    Tuesday, December 10, 2019 1:06 AM
  • Hello,

    So I tested the proposed code and it works like a charm !
    I've just slightly modified it to meet my needs, although it is not optimized in performance.

    Here is what I did: 

    $Group = "test"
    
    $sb = {
    
        for ($i=0; $i-lt (get-hotfix).Count;$i++)
        {
        [pscustomobject]@{
            ComputerName = $env:COMPUTERNAME
            PatchesKB = (Get-HotFix).HotFixID[$i]
            Description = (Get-HotFix).Description[$i]
            InstalledOn = (Get-HotFix).InstalledOn[$i]
            RebootRequired = [bool](Get-Item 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' -ErrorAction SilentlyContinue)
            LastReboot = (Get-CimInstance win32_operatingsystem).LastBootUpTime 
             }
        }
    }
    
        import-csv "C:\Users\User\Desktop\$group.csv" |
        ForEach-Object{
        Invoke-Command -ComputerName $PSitem.Name -ScriptBlock $sb | select -Property * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName | Export-csv -Path "C:\Users\User\Desktop\result_$Group.csv" -NoTypeInformation -Append
    }

    Added a for loop so I could get every element in a separate row of the CSV file.
    Otherwise I get everything on the same box after I open the file in Excel.

    Another thing is I added 

    select -Property * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName

    to a pipeline so I get rid of unnecessary properties.

    Many thanks to all who stepped in to help and special KUDOS to jrv.

    P.S.: Thank you for pointing out  Windows-PowerShell TFM 4 it is an amazing book.





    • Marked as answer by Kushagi Tuesday, December 10, 2019 9:43 AM
    • Edited by Kushagi Tuesday, December 10, 2019 3:12 PM
    Tuesday, December 10, 2019 9:30 AM
  • No necessary. You need to learn how to write a program with PowerShell.  Your guess is close but fails to use the code well and it will be very slow.

    $group = 'groupname'
    $csvin = "C:\Users\User\Desktop\$group.csv"
    $csvpath = "C:\Users\User\Desktop\result_$group.csv"
    $sb = { [pscustomobject]@{ ComputerName = $env:COMPUTERNAME Patches = Get-HotFix RebootRequired = [bool](Get-Item 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update').RebootRequired LastReboot = (Get-CimInstance win32_operatingsystem).LastBootUpTime } } import-csv $csvin | ForEach-Object{ $results = Invoke-Command -ComputerName $_.Name -ScriptBlock $sb $results.Patches | select *, @{n='RebootRequired';e={$results.RebootTime}},@{n='LastReboot';e={$results.LastReboot}} } | Export-csv $csvpath -NoTypeInformation

    After designing a solution in the raw it can be refactored for simplicity and performance.


    \_(ツ)_/



    • Edited by jrv Tuesday, December 10, 2019 9:51 AM
    Tuesday, December 10, 2019 9:48 AM