Fixlet Stuck on Running

Hello everyone! How are you?

I created a PowerShell script (for a Fixlet) to remove .NET in some versions (EOL 7 and 5).

Here is the relevance:

exists key whose(value "DisplayName" of it as string starts with "Microsoft .NET 7.0") of keys "HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall" of (x32 registries; x64 registries) OR exists keys whose(value "DisplayName" of it as string starts with "Microsoft ASP.NET Core 7.0") of keys "HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall" of (x32 registries; x64 registries) OR exists keys whose(value "DisplayName" of it as string starts with "Microsoft .NET Runtime - 7.0") of keys "HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall" of (x32 registries; x64 registries) OR exists keys whose(value "DisplayName" of it as string starts with "Microsoft .NET 5.0") of keys "HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall" of (x32 registries; x64 registries) OR exists keys whose(value "DisplayName" of it as string starts with "Microsoft ASP.NET Core 5.0") of keys "HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall" of (x32 registries; x64 registries) OR exists keys whose(value "DisplayName" of it as string starts with "Microsoft .NET Runtime - 5.0") of keys "HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall" of (x32 registries; x64 registries)

Here is the script:

function UninstallProgram {
    param(
        [string]$programName
    )

    $registryKeys = @(
        "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
    )

    $programsFound = $true

    while ($programsFound) {
        $programsFound = $false

        foreach ($registryKey in $registryKeys) {
            $uninstallKeys = Get-ChildItem $registryKey | Get-ItemProperty

            foreach ($key in $uninstallKeys) {
                if ($key.DisplayName -match $programName) {
                    $uninstallString = $key.UninstallString

                    # Add space before the silent uninstall parameters
                    if ($uninstallString -notmatch "(/quiet|/qn|/silent|/s)") {
                        $uninstallString += " /quiet /norestart /qn"
                    }

                    # Correct command if necessary
                    if ($uninstallString -match "msiexec") {
                        $uninstallString = $uninstallString.Replace("/I{", "/X{")
                    }

                    Write-Host "UninstallString value: $uninstallString"

                    Write-Host "Uninstalling $($key.DisplayName) silently..."
                    Start-Process -FilePath "cmd.exe" -ArgumentList "/c $uninstallString" -WindowStyle Hidden -Wait
                    Write-Host "$($key.DisplayName) uninstalled successfully."

                    # Set flag to true to check for more instances
                    $programsFound = $true
                }
            }
        }
    }

    if (-not $programsFound) {
        Write-Host "$programName not found. No action taken."
    }
}

UninstallProgram "Microsoft .NET 7.0"
UninstallProgram "Microsoft ASP.NET Core 7.0"
UninstallProgram "Microsoft .NET Runtime - 7.0"
UninstallProgram "Microsoft .NET 5.0"
UninstallProgram "Microsoft ASP.NET Core 5.0"
UninstallProgram "Microsoft .NET Runtime - 5.0"

I conducted various tests in different scenarios using the script and relevance, utilizing both the Fixlet Debugger and deploying through BigFix. The tests were performed on my computer and a colleague’s computer. All tests were successful, and all versions of .NET (EOL) were properly removed.

However, when performing the tests on a different server and in a different environment with Windows 11 and BigFix Agent version 10.0.46, the fixlet remained in a running status without any progress on all the machines tested.

Could the issue be related to permissions, interference from firewall or security software?

I appreciate the help in advance, guys!

You’d almost certainly need to run your PowerShell script manually on one of those systems to see what’s happening. It looks like you’re making a lot of assumptions about the Uninstall command lines (for example that it should be msiexec), and if the rest of the UninstallString does not match msiexec parameters you would get a pop-up message about msiexec invalid parameters; since there is no interface to display the message, the script will get “stuck” waiting for an acknowledgement.

1 Like

I made some changes to the script and tested it locally in PowerShell ISE on one of the machines.

Here’s the script used:

function UninstallProgram {
    param(
        [string]$programName
    )
 
    $registryKeys = @(
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
    )
 
    $programsFound = $false
 
    foreach ($registryKey in $registryKeys) {
        $uninstallKeys = Get-ChildItem $registryKey | Get-ItemProperty
 
        foreach ($key in $uninstallKeys) {
            if ($key.DisplayName -match $programName) {
                $programsFound = $true
                $uninstallString = $key.UninstallString
 
                # Check installer type and add appropriate silent parameters
                if ($uninstallString -match "msiexec") {
                    if ($uninstallString -notmatch "(/quiet|/qn|/silent|/s)") {
                        $uninstallString += " /quiet /norestart /qn"
                    }
                    $uninstallString = $uninstallString.Replace("/I{", "/X{")
                } elseif ($uninstallString -match "setup.exe") {
                    if ($uninstallString -notmatch "(/quiet|/qn|/silent|/s)") {
                        $uninstallString += " /quiet /norestart /silent"
                    }
                } elseif ($uninstallString -match ".msi") {
                    if ($uninstallString -notmatch "(/quiet|/qn|/silent|/s)") {
                        $uninstallString += " /quiet /norestart /qn"
                    }
                } else {
                    if ($uninstallString -notmatch "(/quiet|/qn|/silent|/s)") {
                        $uninstallString += " /quiet /norestart"
                    }
                }
 
                # Add quotes if the command contains spaces
                if ($uninstallString -match "\s") {
                    $uninstallString = "`"$uninstallString`""
                }
 
                Write-Host "UninstallString value: $uninstallString"
 
                Write-Host "Uninstalling $($key.DisplayName) silently..."
                try {
                    $process = Start-Process -FilePath "cmd.exe" -ArgumentList "/c $uninstallString" -WindowStyle Hidden -Wait -PassThru
                    Write-Host "Exit code: $($process.ExitCode)"
                    if ($process.ExitCode -eq 0) {
                        Write-Host "$($key.DisplayName) uninstalled successfully."
                    } else {
                        Write-Host "Failed to uninstall $($key.DisplayName). Exit code: $($process.ExitCode)"
                    }
                } catch {
                    Write-Host "Error trying to uninstall $($key.DisplayName): $_"
                }
            }
        }
    }
 
    if (-not $programsFound) {
        Write-Host "$programName not found. No action taken."
    }
}
 
# List of programs to uninstall
$programsToUninstall = @(
    "Microsoft .NET 7.0",
    "Microsoft ASP.NET Core 7.0",
    "Microsoft .NET Runtime - 7.0",
    "Microsoft .NET 5.0",
    "Microsoft ASP.NET Core 5.0",
    "Microsoft .NET Runtime - 5.0"
)
 
foreach ($program in $programsToUninstall) {
    UninstallProgram $program
}

The difference I found was that the .NET in this environment is only present in the x64 registry, and even though the script correctly identifies the UninstallString and executes it, .NET is not uninstalled.

Here’s the output:

Microsoft .NET 7.0 not found. No action taken.
Microsoft ASP.NET Core 7.0 not found. No action taken.
UninstallString value: “MsiExec.exe /X{73F5EDDD-8C52-4F96-92E0-8204159D12C9} /quiet /norestart /qn”
Uninstalling Microsoft .NET Runtime - 7.0.19 (x64) silently…
Exit code: 0
Microsoft .NET Runtime - 7.0.19 (x64) uninstalled successfully.
Microsoft .NET 5.0 not found. No action taken.
Microsoft ASP.NET Core 5.0 not found. No action taken.
Microsoft .NET Runtime - 5.0 not found. No action taken.

So, your modified script has no problem when run manually, but you haven’t tried this version inside a BigFix Action, right?

On the original script –

I’m no PowerShell guru, but why does your 32-bit registry path end with `*’ but the 64-bit does not? Does this look at every subkey of x32, but not look at the subkeys of x64 ?

I have found that nine times out of ten, when a fixlet shows running for a long time, there is a modal window prompt running under the system user that can’t be responded to.

When I am asked to create content for an uninstall or install, I make the requestor provide me with proof that running their provided commands do not produce a prompt.

As @JasonWalker said above, do not make the mistake of assuming uninstall strings in the registries, don’t produce a prompt.

1 Like