1

I try to uninstall a msi file, but when I try this via array I get an error (cant find installation package)

When I do the same but not in array - it works

for ($i=0; $i -lt $msiArrayClean.length; $i++){
  
        Write-Host $msiArrayClean[$i]
        & msiexec.exe /x $msiArrayClean[$i]

}

here the output of Write Host

How i come to $msiArrayClean

 $msiCache = get-wmiobject Win32_Product | Where-Object Name -like "*7-Zip*"  | Format-Table LocalPackage -AutoSize -HideTableHeaders
    $msiString = $msiCache | Out-String
    $msiArrayWithEmptyLines = $msiString -split "`n"
    $msiArray = $msiArrayWithEmptyLines.Split('', [System.StringSplitOptions]::RemoveEmptyEntries)
    $msiArrayCleanString = $msiArray | Out-String
    $msiArrayClean = $msiArrayCleanString -split "`n"

2 Answers2

1

I don't like reaching to WMI, since its perfomance is the issue. I prefer to do it via registry and it worked for me many times. Code explanation in comments.

$name = "7-zip"

#Get all items from registry
foreach ($obj in Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") {
    #Get DisplayName property of registry
    $dname = $obj.GetValue("DisplayName")
    #Search for given name
    if ($dname -like "*$name*") {
        #Get uninstall string (it gets you msiexec /I{APPID})
        $uninstString = $obj.GetValue("UninstallString")
        foreach ($line in $uninstString) {
            #Getting GUID from "{" to "}""
            $found = $line -match '(\{.+\}).*'
            if ($found) {
                #If found - get GUID
                $appid = $matches[1]
                Write-Output "About to uninstall app $appid"
                #Start uninstallation
                Start-Process "msiexec.exe" -arg "/X $appid /qb" -Wait
            }
        }
    }
}

Edit: Added solution with msi path after Nehat's comment as this works for me (I tried to minimize the code :))

$msiCache = get-wmiobject Win32_Product | Where-Object Name -like "*7-Zip*"  | Format-Table LocalPackage -AutoSize -HideTableHeaders

foreach ($msi in $msiCache | Out-String) {
    if ([string]::IsNullOrEmpty($msi)) {
        continue
    }
    Write-Host $msi
    Start-Process "msiexec.exe" -arg "/x $msi" -Wait
}
Karolina Ochlik
  • 908
  • 6
  • 8
  • Nice Solution, but in my case I need the Path to the cached msi File because : some APPs which are installed need the original MSI file when you want to uninstall - that's why I do it via wmi – Nehat Adler Jun 16 '21 at 14:00
  • Thanks you for your support: I get the same message (Installation Package could not be found - but its there ) - when you have some idea, please let me know - thanks for optimizing the code :) – Nehat Adler Jun 16 '21 at 14:38
1

A few caveats up front:

  • Format-* cmdlets output objects whose sole purpose is to provide formatting instructions to PowerShell's output-formatting system - see this answer. In short: only ever use Format-* cmdlets to format data for display, never for subsequent programmatic processing.

  • The CIM cmdlets (e.g., Get-CimInstance) superseded the WMI cmdlets (e.g., Get-WmiObject) in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell (Core) (version 6 and above), where all future effort will go, doesn't even have them anymore. For more information, see this answer.

  • Use of the Win32_Product WMI class is discouraged, both for reasons of performance and due to potentially unwanted side effects - see this Microsoft article.

    • An alternative - available in Windows PowerShell only (not in PowerShell (Core) 7+) - is to use the following to get uninstall command lines and execute them via cmd /c:

      Get-Package -ProviderName Programs -IncludeWindowsInstaller | 
        ForEach-Object { $_.meta.attributes['UninstallString'] }
      

If you need to stick with Win32_Product:

# Get the MSI package paths of all installed products, where defined.
$msiCache = (Get-CimInstance Win32_Product).LocalPackage -ne $null

foreach ($msiPackagePath in $msiCache) {
  if (Test-Path -LiteralPath $msiPackagePath) {
    # Note that msiexec.exe runs *asynchronously*.
    # Use Start-Process -Wait to wait for each call to complete.
    & msiexec.exe /x $msiPackagePath
  } else {
    Write-Warning "Package not found: $msiPackagePath"
  }
}
mklement0
  • 382,024
  • 64
  • 607
  • 775