I'm moving a yaml pipeline so it uses PS Core. One of the steps is to parse currently installed software and uninstall it if it exists:
$appsToUninstall = Get-Package -Provider Programs -IncludeWindowsInstaller -name "*$Instance*"
if ($appsToUninstall.count -gt 0)
{
Get-Service | Where-Object { $_.Name -match "$Instance" } | Stop-Service
$appsToUninstall | Uninstall-Package -Force -Verbose
Write-Output "Uninstalled $Instance"
}
else
{
Write-Warning "Nothing to uninstall! Could not locate $Instance as an installed instance. Continuing as usual."
}
However, it would seem that the new Get-Package - no longer provides installed software.
Is there any way to use native cmdlets (not CMI/WMI [wmi is deprecated!]) to achieve that in the new PS 7+?
You can parse the registry to achieve this :
$Instance = "MyAppToUninstall"
# Initialize array to avoid errors on 32 bits applications addition
[array]$appsToUninstall = #()
# Get 64 bits programs
$appsToUninstall = Get-ItemProperty `
HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select DisplayName, UninstallString |
Where { $_.DisplayName -like "*$Instance*" }
# Add 32 bits programs
$appsToUninstall += Get-ItemProperty `
HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select DisplayName, UninstallString |
Where { $_.DisplayName -like "*$Instance*" }
if ($appsToUninstall.count -gt 0)
{
Get-Service | Where-Object { $_.Name -match "$Instance" } | Stop-Service
$appsToUninstall | ForEach-Object {
$app = $_.UninstallString.Substring(0, $_.UninstallString.IndexOf(".exe") + 4)
$arguments = $_.UninstallString.Substring($_.Uninstallstring.IndexOf(".exe") + 5)
if ($app -match "(?i)msiexec")
{
# if MSI package, replace /i parameter by /x to uninstall and
# add /passive parameter to automate the uninstallation
$arguments = "$($arguments.Replace("/I", "/x", [system.StringComparison]::InvariantCultureIgnoreCase)) /passive"
}
Start-Process -FilePath $app -ArgumentList $arguments
}
Write-Output "Uninstalled $Instance"
}
else
{
Write-Warning "Nothing to uninstall! Could not locate $Instance as an installed instance. Continuing as usual."
}
I hope this helps :)
Related
I'm currently trying to setup a script to uninstall Chrome from PCs to upload onto Intune but following the MS documentation on the parameters of new job triggers just pops up with errors on invalid times. I'll paste below the documentation I'm using to get the parameters, not sure where to go in terms of resolving the error
https://learn.microsoft.com/en-us/powershell/module/psscheduledjob/new-jobtrigger?view=powershell-5.1
Register-ScheduledJob -Trigger $Trigger -RunAs32 -ScriptBlock {
$SEARCH = 'chrome$'
$INSTALLED = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, UninstallString
$INSTALLED += Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, UninstallString
$RESULT = $INSTALLED | ?{ $_.DisplayName -ne $null } | Where-Object {$_.DisplayName -match $SEARCH }
if ($RESULT.uninstallstring -like "msiexec*") {
$ARGS=(($RESULT.UninstallString -split ' ')[1] -replace '/I','/X ') + ' /q'
Start-Process msiexec.exe -ArgumentList $ARGS -Wait
} else {
$UNINSTALL_COMMAND=(($RESULT.UninstallString -split '\"')[1])
$UNINSTALL_ARGS=(($RESULT.UninstallString -split '\"')[2]) + ' --force-uninstall'
Start-Process $UNINSTALL_COMMAND -ArgumentList $UNINSTALL_ARGS -Wait
}
} -Name RemoveChrome```
I'm trying to make a powershell script to uninstall Onedrive from users computers by running Invoke-Command -computername computer1 -filepath script.ps1
but i keep getting issues with the last step.
The first function "doUninstall" works just fine, it runs it and so on.
But the second one "douninstall2" doesnt work, it cant run the file
Anyone have any ideas what im doing wrong and how to fix this?
When i try running it using
Start-Process -filepath $UninstallCommand -ArgumentList "$EXEArgumente" -Wait
I can see the process starting on the target computer but it closes immediately
C:\Users\myuser\AppData\Local\Microsoft\OneDrive\23.002.0102.0004_5\OneDriveSetup.exe /uninstall
DEBUG: 78+ >>>> Write-Output "Uninstalling OneDrive found in Uninstall registry key"
Uninstalling OneDrive found in Uninstall registry key
DEBUG: 79+ >>>> $proc = Start-Process "$UninstallString" -PassThru
DEBUG: 83+ >>>> Write-Output "Uninstall failed with exception $_.exception.message"
Uninstall failed with exception This command cannot be run due to the error: The system cannot find the file specified..exception.message
#Requires -RunAsAdministrator
Set-PSDebug -Trace 2
function doUninstall($check) {
if (Test-Path $check) {
Write-Output "Uninstalling OneDrive found in $check"
$proc = Start-Process $check "/uninstall" -PassThru
$proc.WaitForExit()
}
}
function douninstall2 {
if (Test-Path $UninstallString) {
$UninstallString
Write-Output "Uninstalling OneDrive found in Uninstall registry key"
$proc = Start-Process "$UninstallString" -PassThru
$proc.WaitForExit()
}
else {
write-output "File not found"
}
}
function unstring {
$PatternSID = 'S-1-5-21-\d+-\d+\-\d+\-\d+$'
$username = Read-Host "Enter User ID: "
$ApplicationName = "Microsoft Onedrive"
# Get Username, SID, and location of ntuser.dat for all users
$ProfileList = gp 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*' | Where-Object {$_.PSChildName -match $PatternSID} |
Select #{name="SID";expression={$_.PSChildName}},
#{name="UserHive";expression={"$($_.ProfileImagePath)\ntuser.dat"}},
#{name="$username";expression={$_.ProfileImagePath -replace '^(.*[\\\/])', ''}}
# Get all user SIDs found in HKEY_USERS (ntuder.dat files that are loaded)
$LoadedHives = gci Registry::HKEY_USERS | ? {$_.PSChildname -match $PatternSID} | Select #{name="SID";expression={$_.PSChildName}}
# Get all users that are not currently logged
$UnloadedHives = Compare-Object $ProfileList.SID $LoadedHives.SID | Select #{name="SID";expression={$_.InputObject}}, UserHive, Username
# Loop through each profile on the machine
Foreach ($item in $ProfileList) {
# Load User ntuser.dat if it's not already loaded
IF ($item.SID -in $UnloadedHives.SID) {
reg load HKU\$($Item.SID) $($Item.UserHive) | Out-Null
}
#####################################################################
# This is where you can read/modify a users portion of the registry
# This example lists the Uninstall keys for each user registry hive
"{0}" -f $($item.Username) | Write-Output
$Global:Selection = (Get-ChildItem -Path registry::HKEY_USERS\$($Item.SID)\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object { $_.DisplayName -match "$ApplicationName" } | Select-Object Publisher,DisplayName,Version,UninstallString)
#$Selection2 = (Get-ChildItem -Path registry::HKEY_USERS\$($Item.SID)\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object { $_.DisplayName -match "$ApplicationName" } | Select-Object Publisher,DisplayName,Version,UninstallString)
#####################################################################
$Selection | ForEach-Object {
$Global:UninstallString = $_.UninstallString
}
}
# Unload ntuser.dat
IF ($item.SID -in $UnloadedHives.SID) {
### Garbage collection and closing of ntuser.dat ###
[gc]::Collect()
reg unload HKU\$($Item.SID) | Out-Null
}
}
try {
$check1 = "$ENV:SystemRoot\System32\OneDriveSetup.exe"
$check2 = "$ENV:SystemRoot\SysWOW64\OneDriveSetup.exe"
$check3 = "$ENV:ProgramFiles\Microsoft Office\root\Integration\Addons\OneDriveSetup.exe"
$check4 = "${ENV:ProgramFiles(x86)}\Microsoft Office\root\Integration\Addons\OneDriveSetup.exe"
Write-Output "Stopping OneDrive processes..."
Stop-Process -Name OneDrive* -Force -ErrorAction SilentlyContinue
# Uninstall from common locations
doUninstall($check1)
doUninstall($check2)
doUninstall($check3)
doUninstall($check4)
# Uninstall from Uninstall registry key UninstallString
unstring
douninstall2
}
catch {
Write-Output "Uninstall failed with exception $_.exception.message"
exit 1
}
I've tried multiple solutions, like adding "" around the path, adding in invoke-command inside the code but nothing works for me.
uninstall-package wont work for me here.
I created a script to uninstall an application silently from the reg keys. It's worked for a few different apps without an issue. I'm trying to uninstall brave browser but, I keep getting this error below. I think the reason is because the uninstall string for brave is
C:\Program Files\BraveSoftware\Brave-Browser\Application\95.1.31.91\Installer\setup.exe" --uninstall --system-level.
The --uninstall and --system-level may be causing the error and I'm not to sure how to get around it.
Any ideas are greatly appreciated.
Error Message Below
Start-Process : This command cannot be run due to the error: The system cannot find the file specified.
At line:11 char:40
... isplayName){Start-Process -Wait -FilePath "$remove32" -ArgumentList "
$appName = "brave"
$32bit = get-itemproperty 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall*'| Where-Object { $.DisplayName -match "^$appName"} | Select-Object DisplayName, DisplayVersion, UninstallString| Where-Object { $.DisplayName -match "^$appName"}
$remove32 = $32bit.UninstallString
$64bit = get-itemproperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall*' | Where-Object { $.DisplayName -match "^$appName"} | Select-Object DisplayName, DisplayVersion, UninstallString | Where-Object { $.DisplayName -match "^$appName"}
$remove64 = $64bit.uninstallstring
if ($appName -like $32bit.DisplayName){Start-Process -Wait -FilePath $remove32 -ArgumentList "/S" -PassThru}
else {Start-Process -Wait -FilePath $remove64 -ArgumentList "/S" -PassThru}enter code here
It's kind of a pain, but you need to parse the command from the arguments somehow. Also the silent install option might not be '/S'. I think I've seen '--force-uninstall'. It's easier with msi installs. You can just use uninstall-package.
$uninstallstring = '"C:\Program Files\BraveSoftware\Brave-Browser\Application\95.1.31.91\Installer\setup.exe" --uninstall --system-level'
$split = -split $uninstallstring
& (-join $split[0..1]) $split[2] $split[3]
Note that you can get the uninstallstring from get-package too.
$uninstallstring = get-package *brave* | % { $_.metadata['uninstallstring'] }
I am attampting to create a script to uninstall unwanted instances (or old ones) of software that we use on our workstations. I can't seem to get the filtering right, though.
function Get-InstalledSoftware2 {
[OutputType([System.Management.Automation.PSObject])]
[CmdletBinding()]
param (
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$Name , [string] $OurName
)
$UninstallKeys = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall", "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
$null = New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS
$UninstallKeys += Get-ChildItem HKU: -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'S-\d-\d+-(\d+-){1,14}\d+$' } | ForEach-Object { "HKU:\$($_.PSChildName)\Software\Microsoft\Windows\CurrentVersion\Uninstall" }
if (-not $UninstallKeys) {
Write-Verbose -Message 'No software registry keys found'
} else {
foreach ($UninstallKey in $UninstallKeys) {
# if (!$PSBoundParameters.ContainsKey('OurName')) {
if ($PSBoundParameters.ContainsKey('Name')) {
$WhereBlock = { ($_.PSChildName -match '^{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}}$') -and ($_.GetValue('DisplayName') -like "$Name*") }
} else {
$WhereBlock = { ($_.PSChildName -match '^{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}}$') -and ($_.GetValue('DisplayName')) }
}
$gciParams = #{
Path = $UninstallKey
ErrorAction = 'SilentlyContinue'
}
$selectProperties = #(
#{n='GUID'; e={$_.PSChildName}},
#{n='Name'; e={$_.GetValue('DisplayName')}}
)
Get-ChildItem #gciParams | Where $WhereBlock | Select-Object -Property $selectProperties
# msiexec /x 'GUID' /qn /norestart
}
}
}
# }
Get-InstalledSoftware2 -Name 'ScreenConnect' -OurName 'ScreenConnect Client (b3d049b2cd879dd9)'
with the commands commented out, I get the following output:
GUID Name
---- ----
{80E0C92B-A22E-4CCA-BB15-E7F8CAE95A96} ScreenConnect
{B92DB068-8FAF-4F4E-8ECC-13FF34DA74A5} ScreenConnect Client (b3d049b2cd879dd9)
But if I remove the hashes on the If statement, I get 0 output. Shouldn't I get the 1st result?
GUID Name
---- ----
{80E0C92B-A22E-4CCA-BB15-E7F8CAE95A96} ScreenConnect
Thanks, everyone!
-Dave
Your code looks a bit overly complicated.
It's perhaps easier just to use this to get a list of installed software on a computer:
$Software = Invoke-Command -Computer $Computer -ScriptBlock {Get-CimInstance -ClassName Win32_Product}
From there you can view identifying numbers, etc. It also shows where the local msi is saved to.
$Software | Select-Object Name,IdentifyingNumber,LocalPackage
So an example function could be
param(
[Parameter(Mandatory=$true)]
[string[]]$Name
)
ForEach ($x in $Name) {
[array]$RemoveIDs += Get-CimInstance -ClassName Win32_Product |
Where-Object {$_.Name -match $x}
}
$RemoveIDs | ForEach-Object {
Sleep 10
$msi=$_.LocalPackage
#$msi=$_.IdentifyingNumber
& msiexec /x $msi /qn
}
My code:
Clear-Host
$installed_softwares = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall |
Get-ItemProperty | Where-Object {$_.DisplayName -like "*" } | Select-Object -Property *
$csv = Import-Csv('C:\<...>\softwares.csv')
$softwares_to_remove = #()
foreach ($line in $csv) {
$info = New-Object System.Object
$info | Add-Member -type NoteProperty -name Name -value $line.Software
$info | Add-Member -type NoteProperty -name Version -value $line.Version
$info | Add-Member -type NoteProperty -name Publisher -value $line.Publisher
$softwares_to_remove += $info
}
ForEach ($installed_software in $installed_softwares) {
ForEach ($software_to_remove in $softwares_to_remove) {
If ($software_to_remove.Name -eq $installed_software.DisplayName -And $software_to_remove.Version -eq $installed_software.DisplayVersion -And $software_to_remove.Publisher -eq $installed_software.Publisher) {
Write-Host 'Tring to remove:' $installed_software.DisplayName, $installed_software.DisplayVersion, $installed_software.Publisher, $installed_software.UninstallString
If (($installed_software.UninstallString).ToLower() -match '^msiexec') {
Write-Host 'Skipped: ' $installed_software.DisplayName
Write-Host ''
} Else {
# Working: .\<SOFTWARE>\uninst.exe /S
$full_path = $installed_software.UninstallString
if ($full_path -like '* *') {
if ($full_path -notlike '"*') {
$full_path = '"' + $full_path + '"'
}
}
write-host $full_path, $full_path.GetType()
Invoke-Command -ComputerName localhost -ScriptBlock { cmd /c $using:full_path /S}
Write-Host 'Removed:' $installed_software.DisplayName
Write-Host ''
}
}
}
}
The output:
Tring to remove: BitComet 1.36 1.36 CometNetwork C:\Program Files (x86)\BitComet\uninst.exe
"C:\Program Files (x86)\BitComet\uninst.exe" System.String
Removed: BitComet 1.36
Tring to remove: qBittorrent 3.3.14 3.3.14 The qBittorrent project "C:\Program Files (x86)\qBittorrent\uninst.exe"
"C:\Program Files (x86)\qBittorrent\uninst.exe" System.String
Removed: qBittorrent 3.3.14
Tring to remove: Steam 2.10.91.91 Valve Corporation C:\Program Files (x86)\Steam\uninstall.exe
"C:\Program Files (x86)\Steam\uninstall.exe" System.String
Removed: Steam
Tring to remove: Viber 6.8.6.5 Viber Media Inc. MsiExec.exe /I{05247C1B-0AD7-43B0-B6F9-D29B376ADC9A}
Skipped: Viber
Currently, I'm running it locally via Powershell ISE started as Local Adminisrator, but the idea is to run this remotely when it's done. Sometimes the only software that gets uninstalled after I run this script many times is qBittorrent, but it doesn't do it flawlessly as it leaves the entry inside "Add/Remove Programs" in Control Panel, if it were a manual uninstall this wouldn't happen.
Why this code have this inconsistent behavior? How to fix it?