Getting Installed Packages InSync Across Multiple Machines with the PowerShell version 5 Preview OneGet Module

I presented a session for the Mississippi PowerShell User Group a couple of days ago and I thought I would share a couple of things I showed during that presentation that I hadn't previously blogged about.

I'm starting where I left off during my last blog article about the OneGet module in the PowerShell version 5 preview.

I've stored the names of the packages that are installed on the local computer (PC03) in a variable named Software and created PSSessions to three remote computers (PC04, PC05, and PC06). Those PSSessions have been stored in a variable named Sessions as shown in the following example:

1($software = Get-Package | Select-Object -ExpandProperty Name)
2($Session = New-PSSession -ComputerName pc04, pc05, pc06)

2014-04-09_11-16-44.png

In the previous blog article, the same packages that are installed on PC03 were installed on PC04, PC05, and PC06 using PowerShell remoting and cmdlets from the OneGet module.

Scenario: There has been a recent problem where other admins have made unauthorized changes, adding and removing various packages on PC04, PC05, and PC06.

The Get-Package cmdlet which is part of the new OneGet module can be used to determine what packages are missing and have been added to those machines based off of what's currently installed on the template machine (PC03), all without leaving your desk:

 1Invoke-Command -Session $Session {
 2    $InstalledPackages = Get-Package | Select-Object -ExpandProperty Name
 3    foreach ($package in $Using:software) {
 4        if ($installedPackages -notcontains $package) {
 5            Write-Output "$Env:COMPUTERNAME is missing the $package package"
 6        }
 7    }
 8    foreach ($package in $InstalledPackages) {
 9        if ($Using:software -notcontains $package) {
10            Write-Output "Unauthorized package named $package detected on $Env:COMPUTERNAME"
11        }
12    }
13}

2014-04-09_12-47-51.png

There is indeed a problem which needs to be corrected to get those machines back in sync with what packages should be installed on them. A slight modification to the previous script has been made to not only report the inconsistencies, but to also correct them on the fly:

 1Invoke-Command -Session $Session {
 2    $InstalledPackages = Get-Package | Select-Object -ExpandProperty Name
 3    foreach ($package in $Using:software) {
 4        if ($installedPackages -notcontains $package) {
 5            Write-Output "$Env:COMPUTERNAME is missing the $package package"
 6            Install-Package -Name $package -Force
 7        }
 8    }
 9    foreach ($package in $InstalledPackages) {
10        if ($Using:software -notcontains $package) {
11            Write-Output "Unauthorized package named $package detected on $Env:COMPUTERNAME"
12            Uninstall-Package -Name $package -Force
13        }
14    }
15}

2014-04-09_12-50-01.png

PC04, PC05, and PC06 are now back to having the same packages installed on them as PC03:

1Invoke-Command -Session $Session {Get-Package} | Format-Table -GroupBy PSComputerName

2014-04-09_12-09-35.png

I've gone back and made some more changes to PC04, PC05, and PC06 to see if I could solve this problem without iterating through both sides with a foreach loop which seems inefficient. This time, multiple issues exist on some of the machines:

2014-04-09_13-34-23.png

Notice in the previous example that using the Format-Table cmdlet with the GroupBy parameter didn't properly group them by PSComputerName because we have two groups for PC05, although it appeared to work two examples ago. This is actually a bonus tip. To determine why this occurred, take a look at the help for the GroupBy parameter:

1help Format-Table -Parameter GroupBy

2014-04-09_13-49-29.png

Based on the information in the help for the GroupBy parameter, the results needed to be sorted first (via the Sort-Object cmdlet). We just got lucky in the example where it worked that the results were in the correct order. Moral of the story: When in doubt, read the help.

Now back to getting those packages on PC04, PC05, and PC06 in sync with the ones installed on PC03.

Instead of iterating through all of the items installed on the source and comparing them to the destination and then iterating through them again to compare the destination to the source to figure out what's missing and what's installed that shouldn't be, how about getting the differences and iterating through only the differences one time?

 1Invoke-Command -Session $Session {
 2    $Differences = Compare-Object -ReferenceObject $Using:software -DifferenceObject (Get-Package | Select-Object -ExpandProperty Name)
 3    foreach ($Difference in $Differences) {
 4        if ($Difference.SideIndicator -eq '<=') {
 5            Write-Output "$Env:COMPUTERNAME is missing the $($Difference.InputObject) package"
 6            Install-Package -Name $Difference.InputObject -Force
 7        }
 8        elseif ($Difference.SideIndicator -eq '=>'){
 9            Write-Output "Unauthorized package named $($Difference.InputObject) detected on $Env:COMPUTERNAME"
10            Uninstall-Package -Name $Difference.InputObject -Force
11        }
12    }
13}

2014-04-09_20-37-51.png

That was definitely a more efficient way of accomplishing this task. All of the machines are back in Sync once again as far as the installed packages go:

1Invoke-Command -Session $Session {Get-Package} |
2Sort-Object -Property PSComputerName |
3Format-Table -GroupBy PSComputerName

2014-04-09_14-01-47.png

Notice that in the previous example, the results were sorted via the Sort-Object cmdlet prior to sending them to the Format-Table cmdlet with the GroupBy parameter to prevent the issue of having multiple groups per computer.

µ