Author Posts

August 23, 2018 at 1:52 pm

Hello Experts,

I recently built a function in PS that collections critical/security and application updates from Windows 10/2016 machines. The issue I'm encountering is that my output is generating duplicate KB's. I've tried using Select-Object -Property Name -Unique (Sort-Object as well) to remove the duplicates and the outputs generates a different result.

If anyone has any feedback on how to get the duplicates removed, it would be greatly appreciated.

Thank you,

Vick B.

 (Get-Package).Where({$_.Name -like "*Update*" -or $_.SwidTagText -like "*support.microsoft.com*"}).ForEach({
# Create hashtable array.
    $psobject = [ordered] @{
        ComputerName = $env:COMPUTERNAME;
        KB = [RegEx]::match($PSItem.Name,'(KB[0-9]{6,7})').Groups[1].Value;
        InstalledOn = ([Xml]($PSItem.SwidTagText)).SoftwareIdentity.Meta.Date;
        Description = $PSItem.Name
    }
    # Create psobject
    $output = New-Object -TypeName psobject -Property $psobject
    # Write output
    Write-Output -InputObject $Output
})

August 23, 2018 at 3:04 pm

So, the reason why Select-Object -Property Name -Unique isn't doing what you want is that you're limiting the output to just the Name property. I'm assuming you still need some of the other properties. If you'd select ALL of the properties you need, you'd probably be okay, assuming that duplicate entries are 100% duplicates across all properties.

You're taking a very programmer-y approach, rather than a more PowerShell pipeline-oriented approach; is that mandatory? Your current approach is going to kind of limit you in terms of what you can do "inline" since the C#-style methods you're using don't expose all of PowerShell's functionality.

August 23, 2018 at 5:28 pm

Hi Don,

The approach is not mandatory at all. I don't have any C# knowledge and didn't realize that was the approach I was taking. Based on what you suggested, I've modified and tested this code and it is now giving the output I'm looking for.


(Get-Package | Sort-Object -Property Name -Unique).Where({$_.Name -like "*Update*" -or $_.SwidTagText -like "*support.microsoft.com*"})

Thank you for your feedback!

August 23, 2018 at 5:32 pm

Yeah. so I'd probably refactor that entirely into a pipeline.

Get-Package |

Sort-Object -Property Name -Unique |

Where-Object {$_.Name -like "*Update*" -or $_.SwidTagText -like "*support.microsoft.com*"}

Those .ForEach() and .Where() methods are cute, but they're really their to entice C# people, in my mind. There are some minor performance differences, but sticking with a fully pipelined approach, for me, provides a lot more flexibility because I can drag in commands for which a cute method doesn't exist. This approach also works in older versions of PowerShell, where the .ForEach() and .Where() method doesn't exist.

 

 

August 23, 2018 at 7:26 pm

Thanks Don, I made those corrects based on your feedback, I've also attached the snippet since i only shared part of the function.


Get-Command



	
			
	

August 23, 2018 at 7:29 pm

Re-posting snippet.

Function Get-WUPatches

{

[CmdletBinding()]

Param

(

[Parameter(Position=0,

Mandatory = $false,

ValueFromPipeline = $true,

HelpMessage = "Enter one or more computer names separated by commas.")]

[String[]]

$ComputerName = $env:COMPUTERNAME,

[Parameter(Position=1,

Mandatory = $false,

ValueFromPipeline = $true)]

[System.Management.Automation.Credential()]

$Credential = [System.Management.Automation.PSCredential]::Empty

)

# RECORD BY RECORD PROCESSING BLOCK

PROCESS

{

# PROCESS ALL COLLECTION ITEMS IN AN SEQUENTIAL ORDER

FOREACH ($Computer in $ComputerName)

{

# ERROR HANDLING

TRY

{

# Getting the name of the Operating System

$os = (Get-WmiObject -ComputerName $Computer -ClassName Win32_OperatingSystem -Credential $Credential -ErrorAction Stop).Caption

# Execute cmdlets based on Operating System

If($os -like "*Windows 10*" -or "*Windows Server 2016*")

{

# Local execution

If($env:COMPUTERNAME -match $Computer)

{

# Gather a list of updates

Get-Package |

Sort-Object -Property Name -Unique |

Where-Object {$_.Name -like "*Update*" -or $_.SwidTagText -like "*support.microsoft.com*"} |

ForEach-Object {

# Create hashtable array.

$psobject = [ordered] @{

ComputerName = $Computer;

KB = [RegEx]::match($PSItem.Name,'(KB[0-9]{6,7})').Groups[1].Value;

InstalledOn = ([Xml]($PSItem.SwidTagText)).SoftwareIdentity.Meta.Date;

Description = $PSItem.Name

}

# Create psobject

$output = New-Object -TypeName psobject -Property $psobject

# Write output

Write-Output -InputObject $output

} # End Get-Package, Sort-Object, Where-Object and ForEach-Object cmdlets

} # End If script block

# Remote execution

Else

{

# Start PSRemoting session

Invoke-Command -ComputerName $Computer -Credential $Credential -ScriptBlock {

# Gather a list of updates

Get-Package |

Sort-Object -Property Name -Unique |

Where-Object {$_.Name -like "*Update*" -or $_.SwidTagText -like "*support.microsoft.com*"} |

ForEach-Object {

# Create hashtable array.

$psobject = [ordered] @{

ComputerName = $Computer;

KB = [RegEx]::match($PSItem.Name,'(KB[0-9]{6,7})').Groups[1].Value;

InstalledOn = ([Xml]($PSItem.SwidTagText)).SoftwareIdentity.Meta.Date;

Description = $PSItem.Name

}

# Create psobject

$output = New-Object -TypeName psobject -Property $psobject

# Write output

Write-Output -InputObject $output

}

} -ErrorAction Stop |

Select-Object -ExcludeProperty PSComputerName, RunNameSpaceID

} # End Get-Package, Sort-Object, Where-Object and ForEach-Object cmdlets

} # If Script Block

# Execution for Window 7/2008 R2/2012 R2 or eariler

Else

{

# Query WMI32_QFE for eariler OS's.
Get-WmiObject -ComputerName $Computer -ClassName Win32_QuickFixEngineering -Credential $Credential -ErrorAction Stop | ForEach-Object {
# Create hashtable array.

$psobject = [ordered] @{

ComputerName = $Computer;

KB = $PSItem.HotFixID;

InstalledOn = $PSItem.InstalledOn;

Description = $PSItem.Description

}

# Create psobject

$output = New-Object -TypeName psobject -Property $psobject

# Write output

Write-Output -InputObject $output

} # End Get-WmiObject & ForEach-Object cmdlet

} # ELSE SCRIPT BLOCK

} # TRY SCRIPT BLOCK

CATCH

{

# Show error

Write-Error -Message $PSCmdlet.ThrowTerminatingError($_)

} # CATCH SCRIPT BLOCK

} # FOREACH SCRIPT BLOCK

} # PROCESS SCRIPT BLOCK

} # End Function Get-WUPatches