Deleting user profiles in PowerShell occasionally leaves behind profiles - windows

I have created my first PowerShell script to mass delete user profiles, For the most part it works fine, however, it does occasionally leave behind some profiles that cannot be found in Powershell when Get-CimInstance win32_UserProfile is run.
Even though PowerShell seemingly does not see these profiles they do get touched by the script so that their last accessed date is current, everything aside from their appdata folders are deleted, and they are still in the C:\Users directory.
The only solution I have currently is to manually select these profiles within Users and then delete them but I'm trying to completely automate this process. I am at a loss as to what could cause this, so any advice or guidance is greatly appreciated. Here is my code:
# Grab all the user profiles and ignore anything in our exceptions
$Profiles = Get-CimInstance Win32_UserProfile | where {((!$_.Special) -and ($_.LocalPath -ne "C:\Users\Administrator") -and ($_.LocalPath -ne "C:\Users\admin1") -and ($_.LocalPath -ne "C:\Users\admin2") -and ($_.LocalPath -ne "C:\Users\admin3") -and ($_.LocalPath -ne "C:\Users\admin4"))}
# Get the number of profiles to use in -PercentComplete
$ProfilesCount = $Profiles.Count
# This for loop iterates through profiles and deletes them as well as creates our progress bar
Function ProfilesB-Gone
{
for ($i = 1; $i -lt $ProfilesCount; $i++)
{
# Our progress bar is generated and updated
Write-Progress -Activity 'Removing Profiles' -Status "Deleted $i out of $ProfilesCount profiles" -PercentComplete (($i/$ProfilesCount) * 100)
# Here we're suppressing errors and continuing while deleting our profiles
Remove-CimInstance $Profiles[$i] -EV Err -EA SilentlyContinue
}
# Remove progress bar once complete
Write-Progress -Activity 'Removing Profiles' -Status 'Complete!' -Completed
}
# Call function
ProfilesB-Gone;
# Remove all leftover profiles that remain ocasionally due to noncritical and inconsistent bug and suppress errors. Not suppressing errors causes as many error windows to show as there were $ProfilesCount.
$Profiles | Remove-CimInstance -EV Err -EA SilentlyContinue
# Give success message to inform user of script completion
Write-Host "Profiles Deleted!"

A win10 app (MicrosoftOfficeHub) makes strange links that can't be deleted in the normal way. Using something like cmd /c rmdir /s /q c:\users\user still works, when powershell and wmi don't. How to remove user profile completely from the Windows 10 computer?

Related

fsmgmt.msc | stop process are running from specific directory | Remote server with invoke

I want to stop all the processes are running from specific directory if the process is from C:/CSV/X kill the process the server is remote but I have users are opened the files from different sessions so how can I kill it from different sessions
$files = gci "C:\CSV\X\*"
foreach($file in $files){
Get-Process |
Where-Object {$_.Path -eq $file.FullName} |
Stop-Process -Force -Verbose
}
I tried it and it doesn't work
For example we have a folder named: MyItem Inside the folder there is
MyItem/software.exe
MyItem/MyItem.exe
MyItem/MainMenu.exe
And I look for the processes through this: fsmgmt.msc
So I need to find a way to close all those whose source is MyItem/
If you only want to terminate 'open files' accessed from the network you can do something like this:
#set path
$path = 'C:\CSV\X\'
#get smb open files in specified directory and close em, escape $path \ because of -match operator (regex)
Get-SmbOpenFile | ?{$_.path -match ($path -replace '\\','\\')} | Close-SmbOpenFile
No need to kill processes.

uninstall google chrome using powershell script

I am using the following code to uninstall google chrome software from my remote machines, But this script is executed with no output. And when i check the control panel, still the google chrome program exists. Can someone check this code?
foreach($computer in (Get-Content \path\to\the\file))
{
$temp1 = Get-WmiObject -Class Win32_Product -ComputerName $computer | where { $_.name -eq "Google Chrome"}
$temp1.Uninstall()
}
You shouldn't use the Win32_Product WMI class, one of the side effects of enumeration operations is it checks the integrity of each installed program and performs a repair installation if the integrity check fails.
It is safer to query the registry for this information instead, which also happens to contain the uninstall string for removing the product with msiexec. The uninstall string here will be formatted like MsiExec.exe /X{PRODUCT_CODE_GUID}, with PRODUCT_CODE_GUID replaced with the actual product code for that software.
Note: This approach will only work for products installed with an MSI installer (or setup executables which extract and install MSIs). For pure executable installers which make no use of MSI installation, you'll need to consult the product documentation for how to uninstall that software, and find another method (such as a well-known installation location) of identifying whether that software is installed or not.
Note 2: I'm not sure when this changed but ChromeSetup.exe no longer wraps an MSI as it used to. I have modified the code below to handle the removal of both the MSI-installed and EXE-installed versions of Chrome.
# We need to check both 32 and 64 bit registry paths
$regPaths =
"HKLM:\SOFTWARE\Wow6432node\Microsoft\Windows\CurrentVersion\Uninstall",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
# Since technically you could have 32 and 64 bit versions of the same
# software, force $uninstallStrings to be an array to cover that case
# if this is reused elsewhere. Chrome usually should only have one or the
# other, however.
$productCodes = #( $regPaths | Foreach-Object {
Get-ItemProperty "${_}\*" | Where-Object {
$_.DisplayName -eq 'Google Chrome'
}
} ).PSPath
# Run the uninstall string (formatted like
$productCodes | ForEach-Object {
$keyName = ( Get-ItemProperty $_ ).PSChildName
# GUID check (if the previous key was not a product code we'll need a different removal strategy)
if ( $keyName -match '^{[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}}$' ) {
# Use Start-Process with -Wait to wait for PowerShell to finish
# /qn suppresses the prompts for automation
$p = Start-Process -Wait msiexec -ArgumentList "/l*v ""$($pwd.FullName)/chromeuninst.log"" /X${keyName} /qn" -PassThru
# 0 means success, but 1638 means uninstallation is pending a reboot to complete
# This should still be considered a successful execution
$acceptableExitCodes = 0, 1638
}
else {
Write-Host "Stopping all running instances of chrome.exe, if any are running"
Get-Process chrome.exe -EA Ignore | Stop-Process -Force
# The registry key still has an uninstall string
# But cannot be silently removed
# So we will have to get creating and control the uninstall window with PowerShell
# We need to use the undocumented --force-uninstall parameter, added to below command
$uninstallString = "$(( Get-ItemProperty $_).UninstallString )) --force-uninstall"
# Break up the string into the executable and arguments so we can wait on it properly with Start-Process
$firstQuoteIdx = $uninstallString.IndexOf('"')
$secondQuoteIdx = $uninstallString.IndexOf('"', $firstQuoteIdx + 1)
$setupExe = $uninstallString[$firstQuoteIdx..$secondQuoteIdx] -join ''
$setupArgs = $uninstallString[( $secondQuoteIdx + 1 )..$uninstallString.Length] -join ''
Write-Host "Uninstallation command: ${setupExe} ${setupArgs}"
$p = Start-Process -Wait -FilePath $setupExe -ArgumentList $setupArgs -PassThru
# My testing shows this exits on exit code 19 for success. However, this is undocumented
# behavior so you may need to tweak the list of acceptable exit codes or remove this check
# entirely.
#
$acceptableExitCodes = 0, 19
}
if ( $p.ExitCode -notin $acceptableExitCodes ) {
Write-Error "Program exited with $($p.ExitCode)"
$p.Dispose()
exit $p.ExitCode
}
exit 0
}
Incidentally, if you already know the MSI ProductCode of a given program you don't have to find the uninstall string this way. You can simply execute msiexec /X{PRODUCT_CODE_GUID}.
If you have further problems which aren't caused by the syntax of the above this would be an operational issue and would be better troubleshot over at the https://superuser.com site.
Edit
As discovered via our chat conversation, you are installing per user and with the 79.0.3945.130 version. You can remove per user Chrome with the following command if installed per user (if the version is different you will need the correct version path):
&"C:\Users\username\AppData\Local\Google\Chrome\Application\79.0.3945.130\Installer\setup.exe" --uninstall --channel=stable --verbose-logging --force-uninstall
In the future, it is not recommended to use ChromeSetup.exe or ChromeStandaloneSetup64.exe to install and manage Chrome in an enterprise environment, you should instead use the Enterprise MSI and install system-wide so you can manage Chrome more efficiently. This is the supported way to deploy Chrome in an enterprise environment, and the script I provided will work to uninstall Chrome via msiexec and searching the registry for the {PRODUCT_CODE} as provided.
Assuming it's an msi install and remote powershell is enabled:
invoke-command -computername comp001 { uninstall-package 'google chrome' }
For the programs provider (all users), it's something like:
get-package *chrome* | % { $_.metadata['uninstallstring'] }
"C:\Program Files\Google\Chrome\Application\95.0.4638.54\Installer\setup.exe" --uninstall --channel=stable --system-level --verbose-logging
And then run that uninstallstring, but you'd have to figure out the silent uninstall option (--force-uninstall). It also runs in the background.

Powershell - how to show which computers from a list are running a specific process?

Powershell beginner here working in an air-gapped, Win7 environment with Powershell 4.0 so unable to import any modules or do anything sophisticated but wondering how I can achieve generating a txt file of computers on the network that are running a specific process, say wusa.exe for Windows Updates?
I have a txt list of all the computer names already and so far have this:
$computers = gc "C:\PCList.txt"
foreach ($computer in $computers) {Get-process | out-file -Path "C:\TheseAreRunningWusa.txt"}
But obviously that displays ALL processes, any way to cut out everything aside from a specific one but also only list the ones running said process?
Thanks in advance.
The Get-Process command allows you to specify both a remote computer to run on, and what service you are looking for.
$computers = Get-Content "C:\PCList.txt"
$output = #()
foreach ($computer in $computers) {
if(Get-Process "myProcessName" -ComputerName $computer -ErrorAction SilentlyContinue) {
$output += $computer
}
}
$output | Set-Content "C:\TheseAreRunningMyProcess.txt"
Note: I used -ErrorAction SilentlyContinue as Get-Process throws an error if the process is not found.

running remotely ps commands

i have written a script which acts like a gpo editor, it can get some GPO and OU and link them or unlink them depending on user's wish. now this script does work when its running on Domain Controller machine but i need it to run on a windows 10 machine workstation on the domain. so i need to do the adjusment while showing the user the GUI, all the code must invoke the commands on the dc. i dont know whats the problem but when i enter the commands manually one by one it works and when its running as a script i get errors:
for example here is a function for a link button . (i have a gui with 2 listboxes. one showing the GPO's and one showing the OU (the ou is shown as CanonicalName and not as Distinguishedname hence the $SWITCH variable to go back and forth so the user will see it in a more friendly way)
function LinkFn {
$ResultsTextBox.clear()
#This $SWITCH is used to Translate the user selection from the OU listbox from canonical back to distinguishedname
$SWITCH = Get-ADOrganizationalUnit -filter * -Property CanonicalName | Where-Object {$_.CanonicalName -eq $listBox2.SelectedItem}
ForEach ($line in $listBox1.selecteditems){
try {
Invoke-Command -ComputerName "$DCNAME" -ScriptBlock {New-GPlink -name $line -target $SWITCH -ErrorAction STOP | Out-null}
$ResultsTextBox.AppendText("`n GPO: $line HAVE BEEN LINKED Successfully.`n")
}
catch{
$ResultsTextBox.AppendText("`n$line ALREADY LINKED! TO THIS OU `n")
}}}
can someone help?
From what i see, i think there is a problem with the code line:
$SWITCH = Invoke-Command -ComputerName "$DCNAME" -ScriptBlock {Get-ADOrganizationalUnit -filter * -Property CanonicalName | Where-Object {$_.CanonicalName -eq $listBox2.SelectedItem}}
$switch is coming up empty (where it runs fine on dc), any idea why?
write your try catch block like below. You have to use $using:variable to use the variables declared outside of the scriptblock.;
try {
Invoke-Command -ComputerName "$DCNAME" -ScriptBlock {New-GPlink -name $using:line -target $using:SWITCH -ErrorAction STOP | Out-null}
$ResultsTextBox.AppendText("`n GPO: $line HAVE BEEN LINKED Successfully.`n")
}
catch{
$ResultsTextBox.AppendText("`n$line ALREADY LINKED! TO THIS OU `n")
}
Also, if the user does not have access to connect / remote to the DC, this wont work. User running the script will need admin level access to the DCs or use credentials for account that actually have access.

How to get an UPDATED size of a folder using Powershell

I need to check the size of a specific folder using Powershell. The size of this folder is constantly growing. However, despite this, Powershell always returns the same size.
Example:
Powershell returns 1000079693
wait 5 seconds
Powershell STILL returns 1000079693
The only exceptions to this is if I go to the folder, right click and click properties. I believe this updates something in the OS. Or I can wait a long time (like 5 minutes) After doing this, Powershell will show me a new number for the folder size.
My question is, how can I FORCE Powershell to get the most updated folder size right away, instead of returning the old size?
try{
$size = Get-ChildItem "C:\Users\Administrator\Desktop\GUIClicker\ButtonsThatAreReady" -Recurse -ErrorAction stop |
Measure-Object -property length -sum
Write-Host $size.sum
} catch{
Write-Host ":Error:"
}
Execute this while your script is running (from a separate PowerShell session)
DO {
$a
Get-ChildItem -Path "C:\Users\Administrator\Desktop\GUIClicker\ButtonsThatAreReady" -Force | Measure-Object -Property length -Sum | Select-Object Sum
Start-Sleep -s 1
$a++
} Until ($a -ge 10)
As written this will run for ~10 seconds, substitute the 10 with however many seconds you want to execute for and/or change the sleep from 1 second to however many you need.

Resources