How to run multiple scripts using text files full of references - powershell

I have a script that I feel that I am close to being ready to run but need some help fine tuning things.
My primary objective is this:
From each text file (named after the computer it was generated from), run each script using the data that exists within the .txt file. Each file is output from the C:\Users folder on the computer, listing each user profile that exists on that machine. I need to be able to run the script so that it deletes the specified folders/files for each user profile on that machine.
# Name: CacheCleanup
# Description: Deletes cache files per user on each computer
# Syntax: .\CacheCleanup.ps1
# Author: Nicholas Nedrow
# Created: 06/15/2021
#Text file contains list of all machines that have recently pinged and are online
$Computers = Get-Content "C:\Temp\CacheCleanUp\ComputerUp.txt"
#Users are listed in individual text files assigned with the name of their PC.
$Users = Get-Content "C:\Temp\CacheCleanUp\Computer Users\*.txt"
#Base path for deletion paths
$Path = "\\$PC\c$\users\$user\appdata\local"
#Delete User\Temp files
Remote-Item -Path "$Path\temp\*" -Recurse -Force -EA SilentlyContinue -Verbose
#Delete Teams files
Remove-Item -Path "$Path\Microsoft\Teams" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-ITem -Path "$Path\Microosft\TeamsMeetingAddin" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Microsoft\TeamsPresenceAddin" -Recurse -Force -EA SilentlyContinue -Verbose
#Delete Chrome Cache
Remove-Item -Path "$Path\Google\Chrome\User Data\Default\Cache\*" -Recurse -Force -EA SilentlyContinue -Verbose
#Delete IE Cache
Remove-Item -Path "$Path\Microsoft\Windows\INetCache\*" -Recurse -Force -EA SilentlyContinue -Verbose
#Delete Firefox cache
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\*" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\*.*" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\cache2\entries\*.*" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\thumbnails\*" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\cookies.sqlite" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\webappstore.sqlite" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\chromeapstore.sqlite" -Recurse -Force -EA SilentlyContinue -Verbose
#How to Run each script for each user on each machine
#How to generate detailed log with results of deletion for each section
I will state right away that I am still learning scripting and am unfamiliar with functions, even though I am pretty sure that is what I need to develop here. This is a domain network so the appropriate path for the computer name has been taken into consideration. Each script does run independently, with the computer name specified but I run into issues when it comes to trying to call out each user profile on that computer.
If possible, it would be nice to have some sort of generated report with the outcome of each user profile and what was ran successfully. I don't need to necessarily know every file that was deleted but maybe a list of those files that were unable to be deleted due to conflicts with running programs or permission issues.

You need to use loops. Consider the following code:
$configFiles = "C:\Temp\CacheCleanUp";
Get-Content "$configFiles\TESTComputers.txt" | % {
$PC = $_;
Write-Host "Attempting to clean cache on computer: $PC";
Get-Content "$configFiles\TESTusers.txt" | % {
$user = $_;
$Path = "\\$PC\c$\users\$user\appdata\local"
Write-Host "`tCleaning $Path"
<# Your code goes here #>
}
}
TESTusers.txt contains:
dave
bob
amy
TESTComputers.txt contains:
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4
10.0.0.5
This is the output of the above code and computer/user files:
Attempting to clean cache on computer: 10.0.0.1
Cleaning \\10.0.0.1\c$\users\dave\appdata\local
Cleaning \\10.0.0.1\c$\users\bob\appdata\local
Cleaning \\10.0.0.1\c$\users\amy\appdata\local
Attempting to clean cache on computer: 10.0.0.2
Cleaning \\10.0.0.2\c$\users\dave\appdata\local
Cleaning \\10.0.0.2\c$\users\bob\appdata\local
Cleaning \\10.0.0.2\c$\users\amy\appdata\local
Attempting to clean cache on computer: 10.0.0.3
Cleaning \\10.0.0.3\c$\users\dave\appdata\local
Cleaning \\10.0.0.3\c$\users\bob\appdata\local
Cleaning \\10.0.0.3\c$\users\amy\appdata\local
Attempting to clean cache on computer: 10.0.0.4
Cleaning \\10.0.0.4\c$\users\dave\appdata\local
Cleaning \\10.0.0.4\c$\users\bob\appdata\local
Cleaning \\10.0.0.4\c$\users\amy\appdata\local
Attempting to clean cache on computer: 10.0.0.5
Cleaning \\10.0.0.5\c$\users\dave\appdata\local
Cleaning \\10.0.0.5\c$\users\bob\appdata\local
Cleaning \\10.0.0.5\c$\users\amy\appdata\local
Few things to note about the code:
Get-Content "filename" | % - this is going to loop through the contents of the file one line at a time. % is a shortcut for ForEach-Object.
$_ when inside a foreach loop is an automatic variable created by PowerShell that contains the current item in the loop.
If you have a loop inside a loop and you need to access both $_ values from the inner and outer loop, you can create a new variable (eg $PC = $_;) in the outer loop that can be used within the inner loop (eg $Path = "\\$PC\c$\users\$user\appdata\local").
You should definitely learn to use functions, and then in the future you can combine functions into modules. This is a big help in organising your code, and you can avoid duplication by sharing functions between different scripts - but your current script doesnt need functions (but theyre a good idea).
Depending on your network, you might be able to use PowerShell remoting instead of the Administrative shares to achieve the same effect. This is a more advanced topic, there is some configuration required on the machines you want to connect but the advantage is your computer sends the script to each target, and the target computer runs the script and reports its results.
Another possible change i would suggest is only using a list of computers - then on each computer use get-childitem -path c:\users to actually get the list of each profile currently on that target computer.

Related

Remove alternative data stream using powershell

I'm trying to remove a bunch of OSX alternate data streams on an NTFS volume. However no matter what I try I cannot get Powershell to do it. Yes, I admit that my powershell is not great. Is anyone able to help?
Objective: Remove the ADS "AFP_AfpInfo" from any directory in the volume.
Current Code:
Get-ChildItem E:\ -Directory -Recurse | ForEach-Object {
$streams = Get-Content -Path $_ -Stream AFP_AfpInfo -ErrorAction SilentlyContinue
if ($streams) {
$streams | ForEach-Object {
try {
Remove-Item -Path "$($_.PSPath)" -Stream AFP_AfpInfo -Recurse -Force -ErrorAction Silentlycontinue
}
catch {
Write-Host "An error occurred: $($_.Exception.Message)"
}
}
}
}
Current error:
An error occurred: A parameter cannot be found that matches parameter name 'Stream'.
Note: Running Powershell 7.3
-Recurse and -Stream don't seem to go together even though in the documentation they appear in the same Parameter Sets. In this case -Recurse should be removed. GitHub Issue #9822 was submitted to add clarification to the Remove-Item doc.
Also, you're seeking for an exact stream, AFP_AfpInfo, so I don't see a need to enumerate $streams. Lastly, checking if a file or folder has an alternative stream should be done with Get-Item instead of Get-Content for efficiency.
As a final aside, the code must use the .Remove method from EngineIntrinsics to work, Remove-Item -Confirm:$false -Force will always ask for confirmation on folders, arguably a bug. Remove-Item should skip confirmation checks if -Stream is in use and -Confirm:$false -Force. GitHub issue #19154 was submitted to follow up on this.
$removeFunc = $ExecutionContext.InvokeProvider.Item.Remove
$targetStream = 'AFP_AfpInfo'
Get-ChildItem E:\ -Recurse -Directory | ForEach-Object {
if ($stream = $_ | Get-Item -Stream $targetStream -ErrorAction SilentlyContinue) {
try {
$removeFunc.Invoke($stream.PSPath, $false, $true, $true)
}
catch {
Write-Host "An error occurred: $($_.Exception.Message)"
}
}
}
Why are you not just using the Unblock-File cmdlet to remove ADS?
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/unblock-file?view=powershell-7.3
Description This cmdlet only works on the Windows and macOS platforms.
The Unblock-File cmdlet lets you open files that were downloaded from
the internet. It unblocks PowerShell script files that were downloaded
from the internet so you can run them, even when the PowerShell
execution policy is RemoteSigned. By default, these files are blocked
to protect the computer from untrusted files.
Before using the Unblock-File cmdlet, review the file and its source
and verify that it is safe to open.
Internally, the Unblock-File cmdlet removes the Zone.Identifier
alternate data stream, which has a value of 3 to indicate that it was
downloaded from the internet.
Get-Help -Name Unblock-FIle -Examples
NAME
Unblock-File
SYNOPSIS
Unblocks files that were downloaded from the internet.
------------------ Example 1: Unblock a file ------------------
PS C:\> Unblock-File -Path C:\Users\User01\Documents\Downloads\PowerShellTips.chm
-------------- Example 2: Unblock multiple files --------------
PS C:\> dir C:\Downloads\*PowerShell* | Unblock-File
------------- Example 3: Find and unblock scripts -------------
PS C:\> Get-Item * -Stream "Zone.Identifier" -ErrorAction SilentlyContinue
FileName: C:\ps-test\Start-ActivityTracker.ps1
See also Get-Item, Clear-Content and Remove-Item cmdlets use case:
Friday Fun with PowerShell and Alternate Data Streams
https://jdhitsolutions.com/blog/scripting/8888/friday-fun-with-powershell-and-alternate-data-streams
You could also just use the MSSysinternals tool to remove ADS as well in your PS code.
https://learn.microsoft.com/en-us/sysinternals/downloads/streams

How to get the current script to read off computer names off a txt file located on c:\

I currently have this script working but only able to get it to run locally, I would like to have it read a text file that would be stored on c:\List_of_PCs.txt that would have computer names that it would also run the same script on. That way I can update the text file instead of modify the code.
Set-ExecutionPolicy RemoteSigned
# Get all users
$users = Get-ChildItem -Path "C:\Users"
# Loop through users and delete the Teams file
$users | ForEach-Object {
Remove-Item -Path "C:\Users\$($_.Name)\AppData\Roaming\Microsoft\Teams\Cache\f*" -Force
Remove-Item -Path "C:\Users\$($_.Name)\AppData\Roaming\Microsoft\Teams\Application Cache\Cache\f*" -Force
}
Any help on this I've tried multiple things every which way, I'm sure this is something simple but I'm still very new to PowerShell.
Try something like this...
Requires PowerShell remoting to be enabled and using an account that is an admin on the remote computer
$ComputerList = Import-Csv -Path 'c:\List_of_PCs.txt'
$ComputerList | % {
Invoke-Command -ComputerName $_ -ScriptBlock {
# Set-ExecutionPolicy RemoteSigned # this is something that should be set via GPO for all systems, not your script, so that it is centrally controlled and monitored.
# Get all users
$users = Get-ChildItem -Path "C:\Users"
# Loop through users and delete the Teams file
$users | ForEach-Object {
Remove-Item -Path "C:\Users\$($_.Name)\AppData\Roaming\Microsoft\Teams\Cache\f*" -Force
Remove-Item -Path "C:\Users\$($_.Name)\AppData\Roaming\Microsoft\Teams\Application Cache\Cache\f*" -Force
}
}
}

What command line to delete folder in Appdata folder for local user?

I am trying to test a script to delete a folder in APPdata local folder for a user on machine but testing it locally on my test machine. i cannot use the %userprofile% so technically need to delete a test folder at c:/users/testusername/appdata/local/test
Whats the correct command to delete folders or files from a local user account? I don't want to use the exact name of user.
Also for power shell if i want to delete folders that begin with test_foldername is there a way i can wild card to delete anything with "test_"?
Thanks
Ended up using the following
$users = Get-ChildItem C:\Users
foreach ($user in $users){$folder = "$($user.fullname)\AppData\Local\Test"
If (Test-Path $folder) {Remove-Item $folder -Recurse -Force -ErrorAction silentlycontinue } }
Get-Childitem -Directory -Path $env:LOCALAPPDATA | where {$_.Name -like 'test_*'} | Remove-Item -Force -Recurse -WhatIf
Remove the -WhatIf to actual perform the delete
$env:LOCALAPPDATA will refer to the oaming app data location for the user running the script

Get-ChildItem ignoring "$Recycle.bin" folder?

I've started using a powershell script to clean all files that are older than 30 days in the roaming profiles of all our users.
This is the script I use:
$oldTime = [int]30 #30 days
foreach ($path in Get-Content "pathList.txt") {
Write-Host "Trying to delete files older than $oldTime days, in the folder $path" -ForegroundColor Green
Get-ChildItem $path -Recurse -Verbose | WHERE {($_.CreationTime -le $(Get-Date).AddDays(-$oldTime))} | Remove-Item -Recurse -Force
}
Here is the Pathlist.txt
\\FS001\RDS_FolderRedirection$\*\Downloads
\\FS001\RDS_FolderRedirection$\*\Downloads\$RECYCLE.BIN
For some reason the script ignores the $RECYCLE.BIN folder.. am I missing something here?
Try the -Force param with Get-ChildItem, the Documentation explains it's use as:
Allows the cmdlet to get items that cannot otherwise not be accessed by the user, such as hidden or system files.
It would be used in your code like this:
Get-ChildItem $path -Recurse -Verbose -Force

Compress-Archive Error: Cannot access the file because it is being used by another process

I would like to zip a path (with a service windows running inside).
When the service is stopped, it works perfectly, when the service is running, I have the exception:
The process cannot access the file because it is being used by another
process.
However, when I zip with 7-zip, I don't have any exception.
My command:
Compress-Archive [PATH] -CompressionLevel Optimal -DestinationPath("[DEST_PATH]") -Force
Do you have any idea to perform the task without this exception?
Copy-Item allows you to access files that are being used in another process.
This is the solution I ended up using in my code:
Copy-Item -Path "C:\Temp\somefolder" -Force -PassThru |
Get-ChildItem |
Compress-Archive -DestinationPath "C:\Temp\somefolder.zip"
The idea is that you pass through all the copied items through the pipeline instead of having to copy them to a specific destination first before compressing.
I like to zip up a folder's content rather than the folder itself, therefore I'm using Get-ChildItem before compressing in the last line.
Sub-folders are already included. No need to use -recurse in the first line to do this
A good method to access files being used by another process is by creating snapshots using Volume Shadow Copy Service.
To do so, one can simply use PowerShells WMI Cmdlets:
$Path = "C:/my/used/folder"
$directoryRoot = [System.IO.Directory]::GetDirectoryRoot($Path).ToString()
$shadow = (Get-WmiObject -List Win32_ShadowCopy).Create($directoryRoot, "ClientAccessible")
$shadowCopy = Get-WmiObject Win32_ShadowCopy | ? { $_.ID -eq $shadow.ShadowID }
$snapshotPath = $shadowCopy.DeviceObject + "\" + $Path.Replace($directoryRoot, "")
Now you can use the $snapshotPath as -Path for your Compress-Archive call.
This method can also be used to create backups with symlinks.
From there on you can use the linked folders to copy backed up files, or to compress them without those Access exceptions.
I created a similiar function and a small Cmdlet in this Gist: Backup.ps1
There was a similar requirement where only few extensions needs to be added to zip.
With this approach, we can copy the all files including locked ones to a temp location > Zip the files and then delete the logs
This is bit lengthy process but made my day!
$filedate = Get-Date -Format yyyyMddhhmmss
$zipfile = 'C:\Logs\logfiles'+ $filedate +'.zip'
New-Item -Path "c:\" -Name "Logs" -ItemType "directory" -ErrorAction SilentlyContinue
Robocopy "<Log Location>" "C:\CRLogs\" *.txt *.csv *.log /s
Get-ChildItem -Path "C:\Logs\" -Recurse | Compress-Archive -DestinationPath $zipfile -Force -ErrorAction Continue
Remove-Item -Path "C:\Logs\" -Exclude *.zip -Recurse -Force