Looking to execute a powershell command that will uninstall an application and clear some registry entries if either a file/folder is not present on the system.
Currently looking at something like this:
if(!(test-path “c:\Windows\mdm” )
{
$productdetails=(Get-WmiObject -Query “Select IdentifyingNumber from win32_product where name = ‘Tesapp’“).identifyingnumber
$proc=(Start-Process -FilePath “msiexec.exe” -ArgumentList “/x $productdetails /qn /l*v c:\temp\Uninstall-testapp.log” -Wait -PassThru).ExitCode
if (!($proc -eq 0 -or $proc -eq 3010))
{
exit(1)
}
$RegKey=“HKLM:\SOFTWARE\Microsoft\TestAppManagement\S-0-0-00-0000000000-0000000000-000000000-000\MSI\{0}” -f $productdetails
if(test-path $RegKey)
{
$op= Get-Item $RegKey | Out-File “c:\windows\temp\TestApp_Reg_backup.reg” -Force
$op= Remove-Item $RegKey -Force
}
}
Interestingly if I run the test path it will return false which is expected, or if run the app uninstall/reg clear on its own it will work as intended - however i cannot run them together.
I assume i am making some sort of syntax/ basic logic error?
Related
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 have an executable file (.exe) which has to be run multiple times with different arguments in parallel (ideally on different cores) from a PowerShell script, and at the end wait for all launched executables to terminate. To implement that in my PowerShell script I have used the Start-Job command that runs multiple threads in parallel. And as the script needs to wait for all jobs to finish their execution I used Start-Job in the combination with Get-Job | Wait-Job. This makes the script wait for all of the jobs running in the session to finish:
$SCRIPT_PATH = "path/to/Program.exe"
$jobs = Get-ChildItem -Path $DIR | Foreach-Object {
if ($_ -like "Folder") {
# Do nothing
}
else {
$ARG1_VAR = "Directory\$($_.BaseName)"
$ARG2_VAR = "Directory\$($_.BaseName)\SubDirectory"
$ARG3_VAR = "Directory\$($_.BaseName)\SubSubDirectory"
if (Test-Path -Path $ARG1_VAR)
{
Start-Job -Name -ScriptBlock {
& $using:SCRIPT_PATH -arg1 $using:ARG1_VAR -arg2 $using:ARG2_VAR
}
}
else
{
Start-Job -Name -ScriptBlock {
& $using:SCRIPT_PATH -arg1 $using:ARG1_VAR -arg3 $using:ARG3_VAR
}
}
}
}
$jobs | Receive-Job -Wait -AutoRemoveJob
However, it seems that -FilePath argument of Start-Job does NOT accept .exe files, but only .ps1 files, and therefore I get an exception.
Thus, I decided to use Start-Process command instead which spawns seperate processes instead of seperate threads. But I was not able to find a command that can wait for the termination of all started processed from my script. Therefore, I tried to do it manually by storing all started processes in an array list. And then I tried to wait for each process (using process ID) to terminate. However, that does not seem to work either, because Start-Process -FilePath Program.exe -ArgumentList $ARG_LIST returns NULL, and therefore nothing is saved in the $Process_List.
$SCRIPT_PATH = "path/to/Program.exe"
$procs = Get-ChildItem -Path $DIR | Foreach-Object {
if ($_ -like "Folder") {
# Do nothing
}
else {
$ARG1_VAR = "Directory\$($_.BaseName)"
$ARG2_VAR = "Directory\$($_.BaseName)\SubDirectory"
$ARG3_VAR = "Directory\$($_.BaseName)\SubSubDirectory"
if (Test-Path -Path $ARG1_VAR)
{
$ARG_LIST = #( "-arg1 $ARG1_VAR", "-arg2 $ARG2_VAR")
Start-Process -FilePath $SCRIPT_PATH -ArgumentList $ARG_LIST -PassThru -NoNewWindow
}
else
{
$ARG_LIST = #( "-arg1 $ARG1_VAR", "-arg3 $ARG3_VAR")
Start-Process -FilePath $SCRIPT_PATH -ArgumentList $ARG_LIST -PassThru -NoNewWindow
}
}
}
$procs | Wait-Process
I would appreciate any help. Please note I am using Powershell 5.1, thus ForEach-Object -Parallelconstruct is not supported on my machine.
Thank you!
Regarding your first example with Start-Job, instead of using the -FilePath parameter you could use the -ScriptBlock parameter:
$path = 'path/to/my.exe'
$jobs = Get-ChildItem -Path $DIR | Foreach-Object {
Start-Job -ScriptBlock {
& $using:path -arg1 $using:_ -arg2 $using:ARG2_VAR
}
}
$jobs | Receive-Job -Wait -AutoRemoveJob
Regarding your second example, using Start-Process you should note that, this cmdlet produces no output without the -PassThru switch, hence you're adding effectively nothing to your list.
$processes = Get-ChildItem -Path $DIR | Foreach-Object {
Start-Process -FilePath Program.exe -ArgumentList $ARG_LIST -PassThru
}
With this minor addition of the -PassThru switch you can either use a while loop checking the .HasExited Property of the objects in case you need to do something else with your code while waiting for the processes:
# block the thread until all processes have finished
while($processes.HasExited -contains $false) {
# do something else here if needed
Start-Sleep -Milliseconds 200
}
Or even simpler, as mklement0 points out, if you only need to wait for the processes, you can use Wait-Process:
$processes | Wait-Process
Hi I need to get a script that will do the following:
Check if a service exists
If the service doesn't exist run my script
If the service exists do nothing
Here is what I have but it's not working for me:
$service = Get-WmiObject -Class Win32_Service -Filter "Name='servicename'"
if($service.Status -eq $NULL)
{
$CLID = $inclid
New-Item -Path "c:\" -Name "folder" -ItemType "directory"
Invoke-WebRequest -Uri https://something.com\setup.exe -OutFile c:\folder\swibm#$CLID#101518#.exe
$installer = "swibm#$CLID#101518#.exe"
Start-Process -FilePath $installer -WorkingDirectory "C:\folder"
}
else
{
Write-Host "Client Already Installed"
}
If I run $service.Status alone I get an "OK" returned. Under this condition I would need the script to stop and run the else section. I only want this script to run if $service.Status returns nothing. Where am I going wrong here?
You may try putting $null on the left hand side of the comparison.
If($null -eq $services.status)
Here is a nice write up discussing it
Simpler way to check if service exists:
if( Get-WmiObject -Class Win32_Service -Filter "Name='servicename'" ) {
# Service exists
}
else {
# Service doesn't exist
}
... or use the Get-Service cmdlet:
if( Get-Service -ErrorAction SilentlyContinue -Name servicename ) {
# Service exists
}
else {
# Service doesn't exist
}
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}"
I am currently trying to change the proceedure from this site into a script to automate the process. http://www.freenode-windows.org/resources/vista-7/windows-update
When I check Control Panel> System & Security>Windows Update > View Update History-- updates KB3020369, KB3172605, and KB3125574 do not show up as installed. Is there something wrong with my foreach loop?
<########
CONFIGURATION TO STOP WINDOWS UPDATES
#########>
$rmpth = 'c:\windows\softwaredistribution\WuRedir'
$ws = get-service wuauserv
if($ws.Status -eq "Stopped"){
msg * "Update Service Stopped"
}else{
stop-service wuauserv -Force
msg * "Stopping Update Service, Update Service Stopped"
}
if(test-path $rmpth){
remove-item $rmpth -Force -Confirm:$false
}
<###########
CONFIGURATION TO INSTALL WINDOWS PATCH
###########>
$pathofupdates = #("KB3020369", "KB3172605", "KB3125574")
Foreach($item in $pathofupdates)
{
$wusainit = "/quiet /norestart C:\temp\Windows /extract C:\temp\Windows\${item}.msu"
$disminit = "/online /quiet /norestart /add-package /PackagePath:C:\temp\Windows\${disminit}.cab"
$SB={ Start-Process -FilePath 'wusa.exe' -ArgumentList $wusainit.ToString() -Wait -PassThru }
Invoke-Command -ScriptBlock $SB
$SB={ Start-Process -FilePath 'dism.exe' -ArgumentList $disminit.ToString() -Wait -PassThru }
Invoke-Command -ScriptBlock $SB
}
foreach loop and .msu file order was the issue. Updates had to be in a certain order. renamed updates to 1.KB3020369.msu, 2.KB3172605.msu, and 3.KB3125574.msu.
Found new method for applying .msu updates on
https://harshilsharma63.wordpress.com/2014/12/27/how-to-install-multiple-msu-windows-update-files-with-powershell-script/
<###########
CONFIGURATION TO INSTALL WINDOWS PATCH
###########>
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$dir = (Get-Item -Path $dir -Verbose).FullName
Foreach($item in (ls $dir *.msu -Name))
{
echo $item
$item = $dir + "\" + $item
wusa $item /quiet /norestart | Out-Null
}