#Consolidate simple functions that are nearly identical

47 messages ยท Page 1 of 1 (latest)

hollow burrow
#

I have multiple functions that check if software is installed then writes to a CSV file. Here's two. I will have 10 functions like this. The functions get called in a foreach loop. Everything is functional but I've heard if you have to copy and paste a lot that means you need to rethink your code. I just cant figure out the best way to consolidate these

        function Find-Silverlight { # Detects if silverlight is installed
            [CmdletBinding()]
                param ()
                $silverlight = invoke-command -computername $row.hostname -scriptblock {Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ | 
                    Get-ItemProperty | Select-Object DisplayName, DisplayVersion | where-object DisplayName -like *silverlight*}
                if ($silverlight) {
                    $row.Silverlight = "$($silverlight.DisplayVersion)"
                    write-verbose "$($silverlight.Displayname) $($silverlight.DisplayVersion)"
                } 
                else {
                    write-verbose "Microsoft Silverlight not detected"
                    $row.Silverlight = "Not Detected"
                }
        }

        function Find-Chrome { # Detects if Google Chrome is installed
            [CmdletBinding()]
                param ()
                $chrome = invoke-command -computername $row.hostname -scriptblock {Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ | 
                    Get-ItemProperty | Select-Object DisplayName, DisplayVersion | where-object DisplayName -like *chrome*}
                if ($chrome) {
                    $row.Chrome = "$($chrome.DisplayVersion)"
                    write-verbose "$($chrome.Displayname) $($chrome.DisplayVersion)"
                } 
                else {
                    write-verbose "Google Chrome not detected."
                    $row.Chrome = "Not Detected"
                }
        }
tepid yoke
#

I asked ChatGPT for you ๐Ÿ™‚

You can generalize the two PowerShell functions into a single script that checks for the presence of any specified software by taking the software name as a parameter. Below is the generalized function:

function Find-Software { # Detects if specified software is installed
    [CmdletBinding()]
    param (
        [string]$SoftwareName,
        [string]$ComputerName
    )
    
    $software = Invoke-Command -ComputerName $ComputerName -ScriptBlock {
        param ($SoftwareName)
        Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ |
        Get-ItemProperty | Select-Object DisplayName, DisplayVersion | 
        Where-Object { $_.DisplayName -like "*$SoftwareName*" }
    } -ArgumentList $SoftwareName

    if ($software) {
        $row = @{}
        $row.$SoftwareName = "$($software.DisplayVersion)"
        Write-Verbose "$($software.DisplayName) $($software.DisplayVersion)"
    } 
    else {
        Write-Verbose "$SoftwareName not detected"
        $row = @{}
        $row.$SoftwareName = "Not Detected"
    }
    return $row
}```
tacit comet
#

the chatgpt script would probably work but still not optimal as ideally you would just pass everything you want to find in one cmd

#

so you're not doing a full lookup every time

tepid yoke
#

Then ask GPT to allow to it to accept multiple values from pipeline, and return all hits in a single object.. Or maybe even to accept a list of servers and run queries in parrallel ๐Ÿ™‚

#

Script worked for me:

tacit comet
#

i did say it would work lol

tepid yoke
#
function Find-Software { # Detects if specified software is installed
    [CmdletBinding()]
    param (
        [string]$SoftwareRegex, # Regular expression for software name
        [string]$ComputerName
    )
    
    $software = Invoke-Command -ComputerName $ComputerName -ScriptBlock {
        param ($SoftwareRegex)
        Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ |
        Get-ItemProperty | Select-Object DisplayName, DisplayVersion | 
        Where-Object { $_.DisplayName -match $SoftwareRegex }
    } -ArgumentList $SoftwareRegex

    $result = @{}
    if ($software) {
        foreach ($item in $software) {
            $result.$($item.DisplayName) = $item.DisplayVersion
            Write-Verbose "$($item.DisplayName) $($item.DisplayVersion)"
        }
    } 
    else {
        Write-Verbose "No software matching regex '$SoftwareRegex' detected"
        $result.$SoftwareRegex = "Not Detected"
    }
    return $result
}```
storm vessel
#

three ticks either side for a block @tepid yoke . You can get syntax highlighting if the opening 3 are followed by powershell or ps

tepid yoke
storm vessel
#

fwiw if you do this: ```ps
Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall* |
Select-Object DisplayName, DisplayVersion

You can skip gci. Worth noting that the above only checks one of the hives (32 or 64 bit depending on the PS version running). You'd need to add the Wow6432Node variant to search both.
tepid yoke
#

Good tips.

Yes. I would probably implement some kind of up-check with ping, to see if its online first, before potentioally generating psremoting/winrm timeouts, and I would have parallelised it.

In this case I fed ChatGPT the example scripts to build off. A better way would be to explain that you want a fast and reliable way to get all installed software off windows-servers using powershell remoting, and let it think for itself ๐Ÿ˜„

storm vessel
#

I wouldn't (add the ping check), instead I'd add -ErrorVariable to catch those that don't work. Invoke-Command will run in parallel if you feed it a list of computers

#

in the context of the function above that won't really work, it'd have to be modified somewhat

tepid yoke
#

The point of ping check was to avoid threads waiting for the 3minute default timeout of a failed winrm request, but yes..

storm vessel
#

it tells you ping works, it doesn't tell you if winrm is going to, and you'd have to add code to do that in parallel to make it worthwhile

#

conversely, if ping doesn't work, it doesn't mean wsman is going to fail

tepid yoke
#

That true. Only valid if firewall accepts ICMP vs. all targets.

storm vessel
#

yep, so it's a bit of a "we did that in the year 2000" check much of the time. You can ramp up the -ThrottleLimit on Invoke-Command, you can reduce the timeouts via it's session options, etc, etc. There's a lot of room for optimization within the same protocol.

#

Simplest change to make the sample above run on more than one computer is to make the computer name parameter an array. So ```ps
[string[]]$ComputerName

and you're done for the first candidate.
tepid yoke
#

And accept from pipeline would be nice.. Get-content servers.txt | Find-Software..

storm vessel
#

Absolutely ๐Ÿ™‚

tepid yoke
#

I closed the ChatGPT window. Im leaving it at that, but im very confident it would have made all desired alterations without any big mistakes.
I have written PowerShell manually for 10+ years as a sysadmin, but recently I tested the hype, and it really is a timesaver.

storm vessel
#

heh I won't comment on that one, never used it :

tepid yoke
#

Try it (they have cookies etc.). At first it feels a bit like cheating, but you can still modify the code and format it to your liking, it just gives you a flying start.

tacit comet
#

i use it occasionally but requires experience to utilize effectively. eg in this instance the produced code isn't the solution, the OP still needs some help here to integrate it as the way his current script is formatting data requires more manipulation

tepid yoke
#

I'd say its consolidated at the same functional level ๐Ÿคทโ€โ™‚๏ธ

#

Could it be further improved? Yes.

hollow burrow
#

Thanks for the ideas. I'm going to look at that when I get more time later this afternoon. And yes my current scripts pings first then tests that psremoting is working PowerShell [bool](test-wsman -computername $row.Hostname -erroraction SilentlyContinue)

#

the timeouts for wsman are killer

tepid yoke
#

Test-WSMan.. Nice I did not know..

tacit comet
#

not even improvements, it just lacks the functionality to directly output to his existing data structure

#

fixable pretty easily, but just pointing out why i never point anyone inexperienced to use it

tepid yoke
#

If using PowerShell 7 you can add the parallell parameter to the foreach-object to run the task asyncronously, thus not stopping all work at timeouts.

storm vessel
#

but that's kind of silly

tepid yoke
#

I do agree @storm vessel . It is a valid point to adjust timeout values instead of bothering with testing, but at least this way it tests the actual protocol and not ICMP.
I learned something new. Did not know of test-wsman.

storm vessel
#

I'll grant it's more lightweight than Invoke-Command which must follow through on the connection attempt. But it's turning a possible asynchronous operation into a synchronous one. It's... bothersome.

It is, however, extremely common. It's really hard to conquer the "Gotta test connection first!" path.

tepid yoke
storm vessel
#

fiddles with the script

#

heh 800 chars over... reducing

tepid yoke
#

Cool. What does EnableNetworkAccess do in this script?

hollow burrow
#

had same question lol

tepid yoke
#

Declared but not used?

#

Remotley enable PSRemoting if it fails.. would be nice.

hollow burrow
#

wow! A lot of that goes over my head. I'm going to figure it out though! Thank you so much for taking the time to write that up.