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.
Related
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 :)
i am looking for a Powershell script that can disable all tasks that are "ready" or "running" in a specific folder in the Task Scheduler on 3 or more servers.
After we updated the software i should be able to activate all the tasks that were disabled by the script again, not just activate all disabled scripts, but specificly the ones that were disabled by the script.
I know this should be possible, but i am not capable of assembling the single parts. Everything thats more than a single command is to much for my logic capacitys.
<#getScheduledTasksinfo-FreeholdReboots.ps1
.Synopsis
PowerShell script to list all Scheduled Tasks, the User ID and the State
.DESCRIPTION
This script scans the content of the c:\Windows\System32\tasks and searches the UserID XML value.
The output of the script is a comma-separated log file containing the Computername, Task name, UserID, Status.
.Author
UNKNOWN
.Tweaker
Patrick Burwell
#>
Remove-Item -Force "D:\batch\Logs\Maintenance\$day-SchedTasks-Reboots.csv"
$logfilepath = "D:\batch\Logs\Maintenance\$day-SchedTasks-Reboots.csv"
$ErrorActionPreference = "SilentlyContinue"
$serverlist = gc D:\batch\input\servers.txt
foreach($server in $serverlist){
if($server -like '#*')
{
continue
}
Write-Host $server
$path = "\\" + $server + "\c$\Windows\System32\Tasks"
$tasks = Get-ChildItem -Path $path -File
if ($tasks)
{
Write-Verbose -Message "I found $($tasks.count) tasks for $server"
}
foreach ($item in $tasks)
{
if($item -like 'Optimize Start Menu*'){continue}
if ($item -like "User_Feed_Synchronization*"){continue}
if($item -like 'ComputeSensorWatchDog*'){continue}
if ($item -like "Google*"){continue}
$AbsolutePath = $path + "\" + $item.Name
$task = [xml] (Get-Content $AbsolutePath)
$states = (Get-ScheduledTask -Verbose -TaskPath '\' -TaskName "reboot")
[STRING]$check = $task.Task.Principals.Principal.UserId
[STRING]$state = $states.State
if ($task.Task.Principals.Principal.UserId)
{
if($item -ilike "*reboot*"){
Write-Verbose -Message "Writing the log file with values for $server"
Add-content -path $logfilepath -Value "$server,$item,$check,$state"
}
else {continue}
}
}
}
This script makes changes to all users' profiles.
Here is the script:
# Get each user profile SID and Path to the profile
$UserProfiles = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*" |
Where {$_.PSChildName -match "S-1-5-21-(\d+-?){4}$" } |
Select-Object #{Name="SID"; Expression={$_.PSChildName}}, #{Name="UserHive";Expression={"$($_.ProfileImagePath)\NTuser.dat"}}
# Loop through each profile on the machine
foreach ($UserProfile in $UserProfiles) {
# Load User ntuser.dat if it's not already loaded
if (($ProfileWasLoaded = Test-Path Registry::HKEY_USERS\$($UserProfile.SID)) -eq $false) {
Start-Process -FilePath "CMD.EXE" -ArgumentList "/C REG.EXE LOAD HKU\$($UserProfile.SID) $($UserProfile.UserHive)" -Wait -WindowStyle Hidden
}
}
# Manipulate the registry
$key = "Registry::HKEY_USERS\$($UserProfile.SID)\Software\SomeArchaicSoftware\Configuration"
New-Item -Path $key -Force | Out-Null
New-ItemProperty -Path $key -Name "LoginURL" -Value "https://www.myCompany.local" -PropertyType String -Force | Out-Null
New-ItemProperty -Path $key -Name "DisplayWelcome" -Value 0x00000001 -PropertyType DWORD -Force | Out-Null
$key = "$key\UserInfo"
New-Item -Path $key -Force | Out-Null
New-ItemProperty -Path $key -Name "LoginName" -Value "$($ENV:USERDOMAIN)\$($ENV:USERNAME)" -PropertyType STRING -Force | Out-Null
# Unload NTuser.dat
if ($ProfileWasLoaded -eq $false) {
[GC]::Collect()
Start-Sleep 1
Start-Process -FilePath "CMD.EXE" -ArgumentList "/C REG.EXE UNLOAD HKU\$($UserProfile.SID)" -Wait -WindowStyle Hidden| Out-Null
}
I only need changes to the current logged on user HKEY_USERS hive.
Can anyone help me change the script so it's only the current logged in user who gets the changes?
You can determine the SID of a currently logged-in user via WMI. Check for the owner of a running explorer.exe process, then resolve the account name to its SID:
$user = (Get-WmiObject Win32_Process -Filter "Name='explorer.exe'").GetOwner()
$fltr = "Name='{0}' AND Domain='{1}'" -f $user.User, $user.Domain
$sid = (Get-WmiObject Win32_UserAccount -Filter $fltr).SID
Still, I think a logon script would be a better place for changes to a user's registry settings.
I am trying to start a process and wait for the exit code. The process starts msiexec with an argument list. When i run my script it comes up with the argument help wizard, however if i run the command directly in CMD generated by:
write-host $command
It works as expected. Here is my full script:
# Get uninstall strings from registry, looking for the msiexec option
$applist = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall |
Get-ItemProperty |
Where-Object {$_.DisplayName -match "Microsoft Visio Standard 2013" -and $_.UninstallString -match "msiexec"} |
Select-Object -Property DisplayName, UninstallString
# Check for any aplications requiring uninstall and output their names
if ($applist) {
foreach ($app in $applist) {
Write-host "$($app.DisplayName) has been detected for uninstall"
}
Write-host "Attempting to uninstall application(s)"
# Uninstall each application that has been identified
foreach ($app in $applist) {
try {
$uninst = $app.UninstallString
$pos = $uninst.IndexOf(" ")
$leftPart = $uninst.Substring(0, $pos)
$rightPart = $uninst.Substring($pos+1)
$command = """$rightPart /qn /L*V ""C:\UninstallVisio.txt"""""
write-host $command
$uninstall = (Start-Process "msiexec.exe" -ArgumentList $command -Wait -Passthru).ExitCode
if($uninstall.ExitCode -ne 0) {
write-host "attempting XML config uninstall"
#**still to be worked on**
}
} catch {
write-host "Unable to uninstall $_.Name Please view logs"
Continue
}
}
}
Exit
# Exit script as no apps to uninstall
else {
write-host "No application(s) detected for uninstall"
Exit
}
Instead of this
$command = "$uninst /qn /L*V ""C:\UninstallVisio.txt"""
try
$command = #(
$uninst
"/qn"
"/L*V"
'"C:\UninstallVisio.txt"'
)
Source: see the last example
https://kevinmarquette.github.io/2016-10-21-powershell-installing-msi-files/
#Get uninstall strings from registry, looking for the msiexec option
$applist = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall |
Get-ItemProperty |
Where-Object {$_.DisplayName -match "Microsoft Visio Standard 2013" -and $_.UninstallString -match "msiexec"} |
Select-Object -Property DisplayName, UninstallString
#Check for any aplications requiring uninstall and output their names
if ($applist){
foreach ($app in $applist){
Write-host "$($app.DisplayName) has been detected for uninstall"
}
Write-host "Attempting to uninstall application(s)"
#Uninstall each application that has been identified
foreach ($app in $applist){
try
{
$uninst = $app.UninstallString
$pos = $uninst.IndexOf(" ")
$leftPart = $uninst.Substring(0, $pos)
$rightPart = $uninst.Substring($pos+1)
$command = #(
$rightPart
"/qn"
"/L*V"
'"C:\UninstallVisio.txt"'
)
write-host $command
$uninstall = (Start-Process "msiexec.exe" -ArgumentList $command -Wait -Passthru).ExitCode
If($uninstall.ExitCode -ne 0){
write-host "attempting XML config uninstall"
}
}
catch{
write-host "Unable to uninstall $_.Name Please view logs"
Continue
}
Exit
}
}
#Exit script as no apps to uninstall
else {
write-host "No application(s) detected for uninstall"
Exit
}
It looks like you are trying to run msiexec.exe with ArgumentList "msiexec.exe /x {ProductCode}".
Powershell is trying to run your command as "msiexec.exe msiexec.exe /x {ProductCode}"
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?