Streamline repeating sections

I was wondering how would I streamline this further?

With a 64-bit OS I have to look in the Wow6432node section of the registry as well but there's a fair amount of repeating code.

$SOFTWARE = 'hp'
$OSbit = Get-WmiObject win32_operatingsystem | Select-Object -ExpandProperty osarchitecture 

if ($OSbit -eq '64-bit') {
    Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*, HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | 
    Select-Object DisplayName, DisplayVersion, uninstallstring |
    Where-Object {$_.DisplayName -like "*$software*"} 
} else {
    Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | 
    Select-Object DisplayName, DisplayVersion, uninstallstring |
    Where-Object {$_.DisplayName -like "*$software*"} 
}

Posted Nov 30, 2017, 11:42 AM UTC · edited

28
sk82jack•

Perhaps something like

$SOFTWARE = 'hp'
$OSbit = Get-WmiObject win32_operatingsystem | Select-Object -ExpandProperty osarchitecture 
$RegPath = [System.Collections.ArrayList]@('HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*')

if ($OSbit -eq '64-bit') {
    [void]$RegPath.Add('HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*')
}

Get-ItemProperty $RegPath | 
    Select-Object DisplayName, DisplayVersion, uninstallstring |
    Where-Object {$_.DisplayName -like "*$software*"}
3
tapthrowOP•

Oh I like it - thanks very much!

2
NathanielArnoldR2•
Clear-Host

$softwarePattern = "*Dell*"

$regPaths = @(
  "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\"
  "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
) | Where-Object {Test-Path -LiteralPath $_}

Get-ChildItem -LiteralPath $regPaths |
  Get-ItemProperty |
  Where-Object {$_.DisplayName -like $softwarePattern} |
  Select-Object DisplayName,DisplayVersion,UninstallString
  1. I've abandoned the WMI call for OS architecture, instead only querying the bare existence of the "SysNative" and "32-on-64-bit" paths.

  2. No wildcards in *-Item* -Path parameters, for personal preference. Far easier to control effect and trace cause after the fact by using literal paths with Get-ChildItem, -Recurse if needed, and Where-Object filtering as necessary. :-)

  3. Not a huge fan of appending wildcard characters to existing strings for -like comparison. Philosophically, this suggests you are unnecessarily seizing control that should be delegated to the consumer of the script, if possible.

3
tapthrowOP•
  1. Ah yeah this makes a lot of sense. I like the efficiency. I didn't realise test-path could be combined in that way.
  2. I think I see now that it is a bit unnecessary.
  3. Oh yeah you're totally right.

Thanks for the insight.

2
tapthrowOP•

I've added software versioning to the code and seems good to go but is there anything I'm not seeing?

$softwarePattern = "*Hp Softpaq*"
$softwareVersion = "4.3.20.0"

$regPaths = @(
"HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\"
"HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
) | Where-Object {Test-Path -LiteralPath $_}

Get-ChildItem -LiteralPath $regPaths |
Get-ItemProperty |
Where-Object {$_.DisplayName -like $softwarePattern -and $_.DisplayVersion -ge [version]$softwareVersion} |
Select-Object DisplayName,DisplayVersion,UninstallString 
2
NathanielArnoldR2•

"5" -ge [int]1000

Don't have much time right now -- it's the morning rush at my 8-to-5 -- but I believe you're setting yourself up for this logic quirk except with versions: PowerShell doesn't care that 1000 is an [int]; it's going to cast it to [string] for comparison with "5".

The sanest way to handle this is to move your Select-Object above your Where-Object, and define a calculated property to store the [version] casting within the object itself for the comparison. My own preference, however, is not to use Select-Object at all when calculated properties come into play; instead, I pipe to a ForEach-Object that emits a reconstituted object.

If I have time, I'll demonstrate later today. :-)

3
Lee_Dailey[grin]•

howdy tapthrow,

take a look at using [version]'4.3.20.0' for dealing with version numbers. it's really pretty spiffy! [grin]

take care,
lee

1
NathanielArnoldR2•

As promised, albeit a day later as I wait for ~40 systems to download and install the v1709 Feature Update over a crappy internet connection at my weekend job...

Inserting the [version] equivalent of the [string] DisplayVersion as a calculated property:

Clear-Host

$namePattern = "*Intel*"
$minVersion = "11.0.0.0"

$regPaths = @(
  "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\"
  "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
) | Where-Object {Test-Path -LiteralPath $_}

$versionProp = @{
  Name = "Version"
  Expression = {
    $version = $null

    if ([version]::TryParse($_.DisplayVersion, [ref]$version)) {
      $version
    }
  }
}

Get-ChildItem -LiteralPath $regPaths |
  Get-ItemProperty |
  Select-Object DisplayName,DisplayVersion,UninstallString,$versionProp |
  Where-Object {$_.DisplayName -like $namePattern -and $_.Version -gt $minVersion}

Note that since $_.Version is natively a [version] type, PowerShell's automatic casting will work for you: though $minVersion is natively a [string] type, it will be cast to [version] for the comparison.

Reconstituting the object using Select-Object with calculated properties:

The only problem is that this syntax is complex, verbose, and does not naturally scan from top to bottom. What if you wanted to make other changes to the object, like for example renaming DisplayName to Name, or DisplayVersion to VersionString?

Clear-Host

$namePattern = "*Intel*"
$minVersion = "11.0.0.0"

$regPaths = @(
  "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\"
  "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
) | Where-Object {Test-Path -LiteralPath $_}

$props = @()
$props += @{
  Name = "Name"
  Expression = "DisplayName"
}
$props += @{
  Name = "Version"
  Expression = {
    $version = $null

    if ([version]::TryParse($_.DisplayVersion, [ref]$version)) {
      $version
    }
  }
}
$props += @{
  Name = "VersionString"
  Expression = "DisplayVersion"
}
$props += "UninstallString"

Get-ChildItem -LiteralPath $regPaths |
  Get-ItemProperty |
  Select-Object $props |
  Where-Object {$_.Name -like $namePattern -and $_.Version -gt $minVersion}

That's just about as clearly and cleanly as you can render the above, but it's still too ugly for my taste.

Reconstituting the object using ForEach-Object:

Clear-Host

$namePattern = "*Intel*"
$minVersion = "11.0.0.0"

$regPaths = @(
  "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\"
  "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
) | Where-Object {Test-Path -LiteralPath $_}

Get-ChildItem -LiteralPath $regPaths |
  Get-ItemProperty |
  ForEach-Object {
    $outObj = [PSCustomObject]@{
      Name            = $_.DisplayName
      Version         = $null
      VersionString   = $_.DisplayVersion
      UninstallString = $_.UninstallString
    }

    $version = $null

    if ([version]::TryParse($_.DisplayVersion, [ref]$version)) {
      $outObj.Version = $version
    }

    $outObj
  } |
  Where-Object {$_.Name -like $namePattern -and $_.Version -gt $minVersion}
1