PowerShell Delete folder if exists - powershell

Could you help me again with a powershell script?
I want to check if multiple folders exist, if they exist then delete the complete folder.
Also provide information if the folder has been deleted or information if the folder does not exist.
I now use the script below for multiple files. (thanks to good help)
I want to use the same script for 1 or more folders.
For example, delete folder c:\test1\ and c:test2
Folders may be deleted, even if they still contain files.
$paths = "c:\test\1.txt", "c:\test\2.txt", "c:\test\3.txt"
foreach($filePath in $paths)
{
if (Test-Path $filePath) {
Remove-Item $filePath -verbose
} else {
Write-Host "Path doesn't exits"
}
}
I'm not super handy with powershell, hope you can help me with this again.
Thanks
Tom

To remove a directory (folder) that has content, you must use the -Recurse switch with Remove-Item - otherwise, an interactive confirmation prompt is presented.
A given path existing doesn't necessarily mean that it is a directory - it may be a file. To specifically test if a given path is a directory / file, use -PathType Container / -PathType Leaf with Test-Path.
While only strictly necessary when paths happen to contain [ characters, the robust way to pass literal paths is via the -LiteralPath parameter that file-processing cmdlets support - by contrast, the first positional argument typically binds to the -Path parameter (e.g., Test-Path foo is the same as Test-Path -Path foo), which interprets its argument(s) as wildcard expressions.
Applied to your use case (note that no attempt is made to distinguish files from directories):
# Input directory paths.
$paths = 'c:\test1', 'c:\test2', 'c:\test3'
foreach ($path in $paths) {
if (Test-Path -LiteralPath $path) {
Remove-Item -LiteralPath $path -Verbose -Recurse -WhatIf
} else {
"Path doesn't exist: $path"
}
}
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
Another, more efficient option is to use Get-Item to get objects representing the file-system items, if they exist, and pipe them to Remove-Item:
$paths = 'c:\test1', 'c:\test2', 'c:\test3'
Get-Item -LiteralPath $paths -ErrorAction SilentlyContinue -ErrorVariable errs |
Remove-Item -Recurse -Verbose -WhatIf
if ($errs) {
"The following path(s) do not exist: $($errs.TargetObject)"
}
Note the use of -ErrorAction SilentlyContinue to silence errors resulting from nonexistent paths, and -ErrorVariable errs in order to collect these errors in self-chosen variable $errs.
The .TargetObject property of the [System.Management.Automation.ErrorRecord] instances collected in $errs then contains the path that triggered the error, resolved to a full path.

Related

Is there a way to add a postfix to this script?

So I asked here before about helping with a script to copy and paste files from one folder to another.
However, after I was done, I found that some of the files went missing. I had 600,393 files but when I checked my new folder it only had 600,361 files.
I think it may have been overwritten by duplicates even though the naming convention was supposed to stop those kinds of problems.
Here's the script
$destfolder = '.\destfolder\'
Get-ChildItem -Recurse -File .\srcfolder\ |
Invoke-Parallel {
$_ | Copy-Item -Destination (
Join-Path $using:destfolder ($_.directory.parent.name, $_.directory.name, $_.name -join '-')
) -Verbose -WhatIf
}
(Thanks to the great dudes on r/software, r/Techsupport, and mklement0)
So is there a way to add a postfix that adds a 0 to the name of any file that has the same name as a file already in a folder?
like directory-subdirectory-0-filename.ext
EDIT- Problem is all the files are read-only not hidden, I don't want any hidden files.
Note that Get-ChildItem doesn't include hidden items by default, which may explain at least part of the the discrepancy.
Use the -Force switch to include hidden items.
Separately / additionally, you can deal with name collisions as follows:
$destfolder = '.\destfolder\'
Get-ChildItem -Force -Recurse -File .\srcfolder\ |
Invoke-Parallel {
$newName = Join-Path $using:destfolder ($_.directory.parent.name, $_.directory.name, $_.name -join '-')
# Try to create the file, but only if it doesn't already exist.
if ($null -eq (New-Item $newName -ErrorAction SilentlyContinue)) {
# File already exists -> create duplicate with GUID.
$newName += '-' + (New-Guid)
}
$_ | Copy-Item -Destination $newName -Verbose
}
Note:
With multi-threaded execution, assigning sequence numbers to duplicates would be a nontrivial undertaking, as each thread would have to "reserve" a sequence number and ensure that no other thread claims it before copying to a file incorporating this number is complete.
To avoid such challenges, the above approach simply appends a - plus a GUID to the target file name.

Searching for only folders that contain a certain folder using powershell

I am trying to use powershell to update some programs for my company. I am writing a script to do so (as instructed). When I install the new version of the program on the machines, it also requires me to 'upgrade' existing folders to match the new version of the software.
I need to find all of the folders that contain a certain hidden folder(let the name of said folder be .blah). I am trying to use the get-childitem command, with -path [drives to check] -Recurse -Directory -Force -EA SilentlyContinue. However, I am not sure how to filter correctly to only find folders that contain the .blah folder inside of it.
Help would be very much appreciated.
Combine your Get-ChildItem call with a Where-Object call that tests for a child directory of a given name using Test-Path:
# Note: "." refers to the *current* directory
# Adjust as needed.
Get-ChildItem -LiteralPath . -Recurse -Directory -Force -ErrorAction Ignore |
Where-Object {
Test-Path -ItemType Container -LiteralPath "$($_.FullName)\.blah"
}
The Get-ChildItem call outputs all directories (-Directory) in the entire directory subtree (-Recurse), including hidden ones (-Force), ignoring any errors (such as from lack of permissions, -ErrorAction Ignore).
The Where-Object call calls Test-Path to look for a .blah child directory (-ItemType Container) in the directory at hand ($_).
With a -LiteralPath argument, Test-Path finds the specified path if it exists, irrespective of whether the target file or directory is hidden.
By contrast, with a wildcard-based -Path argument, hidden items are not found, and given that, as of PowerShell 7.2.5, Test-Path has no -Force switch, there is no way to force their inclusion; this gap in functionality is the subject of GitHub issue #6501.
Note: In PowerShell (Core) 7+, you could simplify "$($_.FullName)\.blah" to "$_\.blah", because the [System.IO.DirectoryInfo] and [System.IO.FileInfo] instances output by Get-ChildItem and Get-Item there consistently stringify to their full path (.FullName) property, unlike in WindowsPowerShell, where they situationally stringify by their file/directory name only - see this answer.

Cannot find path when using remove-item cmdlet

I wrote out a simple PowerShell script that backs up a directory to C:\ and then deletes any of the backup folders when its age = X days.
For some reason, when I use the Remove-Item cmdlet I'm getting a Remove-Item: Cannot find path 'C:\Windows\system32\ [Sub-Folder name]' because it does not exist error.
Below is the snippet:
$TargetFolder = "C:\Folder\"
$Folders = get-childitem -path $TargetFolder
foreach ($Folder in $Folders)
{
remove-item $Folder -recurse -force
}
Within the $TargetFolder = "C:\Folder\", there are a few sub-folders.
Examples: C:\Folder\SubfolderA, C:\Folder\SubfolderB, etc.
When I do a Write-Host for $Folder it lists SubFolderA, SubFolderB, etc, correctly so I'm not exactly sure why I'm getting a Cannot find path error.
It seems that you want to do this on the basis of the directory LastWriteTime, but you did not mention -Directory on Get-ChildItem.
[cmdletbinding()]
Param()
$TargetFolder = "C:\Users\lit\Documents"
$Folders = Get-ChildItem -Path $TargetFolder -Directory
$Days = 80
foreach ($Folder in $Folders) {
if ($Folder.LastWriteTime -lt (Get-Date).AddDays(-$Days)) {
Write-Verbose "Deleting directory $($Folder.FullName)"
Remove-Item -WhatIf "$($Folder.FullName)" -Recurse -Force
}
}
tl;dr
To ensure that Remove-Item correctly identifies a directory object as returned by Get-ChildItem (an instance of type [System.IO.DirectoryInfo]):
When passing the object as a parameter value (argument), in Windows PowerShell (no longer in PowerShell Core) you must use .FullName for the command to work reliably:
Remove-Item -LiteralPath $Folder.FullName ... # !! Note the need for .FullName
-LiteralPath is not strictly needed, but is the more robust choice, given that Remove-Item $Folder.FullName implicitly binds to the -Path parameter instead, which interprets its argument as a wildcard expression; often this will make no difference, but it can.
When using the pipeline, you can pass the objects as-is.
Get-ChildItem -Directory | Remove-Item ...
The surprising need to use .FullName is the result of a design quirk; the resulting behavior and its implications are discussed below; a fix has been proposed in this GitHub issue.
Liturgist's helpful answer and AJK's helfpul answer contain complementary pieces of the solution (Liturgist's answer has since been amended to provide a complete solution):
To limit what Get-ChildItem returns to directories (folders), you must use -Directory (PSv3+).
To unambiguously identify a filesystem object when passing it to Remove-Object as a parameter that is converted to a string, its full path must be specified.
Note: The situational filename-only stringification described below affects only Windows PowerShell; fortunately, the problem has been fixed in PowerShell Core, fortunately.
In the case at hand, given that $Folder contains a [System.IO.DirectoryInfo] object as returned by Get-ChildItem, $Folder.FullName is simplest (but constructing the path by prepending $TargetPath works too).
In Windows PowerShell, even though $Folder is a [System.IO.DirectoryInfo] object that does contain the full path information, when converted to a string it situationally may only expand to its mere directory name (last path component) - it depends on how the [System.IO.DirectoryInfo] and [System.IO.FileInfo]instances were obtained, namely:
If Get-ChildItem was called either without a path argument or via a path argument that is a directory, the output objects stringify to their mere file/directory name - see this answer for details.
Simple example: $d = (Get-ChildItem -Directory $HOME)[0]; "$d" yields Contacts, for instance, not C:\Users\jdoe\Contacts.
By contrast, if you pass such objects via the pipeline to Remove-Item, PowerShell does use the full path.
Important: If you do pass the target folder as a parameter and neglect to specify the full path while not in the same location as the target folder, the name may therefore interpreted as relative to the current location, and you'll either get a Cannot find path error - as you saw - or, even worse, you may end up deleting a different folder by the same name if one happens to be present in your current location (directory).
As stated, you can avoid the full-path problem by piping the folder objects returned by Get-ChildItem to Remove-Item, which also enables a more elegant, single-pipeline solution (PSv3+):
$TargetFolder = "C:\Folder"
$Days = 5
Get-ChildItem -Directory $TargetFolder |
Where-Object LastWriteTime -lt (Get-Date).Date.AddDays(-$Days) |
Remove-Item -WhatIf -Force -Recurse
Remove the -WhatIf to perform actual removal.
Try executing remove-item on the full path, e.g.
$TargetFolder = "C:\Folder\"
$Folders = get-childitem -path $TargetFolder
foreach ($Folder in $Folders)
{
remove-item $TargetFolder$Folder -recurse -force
}

Define folder depth for the verbose option in Copy-Item cmdlet

I'm using the following command to copy a directory tree from one folder to another.
Copy-Item $SOURCE $DEST -Filter {PSIsContainer} -Recurse -Force -Verbose
The verbose option is correctly showing each folder that is copied. However, I would like to tell the Verbose option to only shows the first level of the subfolders that are copied. Hence the subfolders/subfolders/... etc wouldn't appear.
Is it possible?
Instead of using the -Verbose option, you could use the -PassThru option to process the successfully processed items via the pipeline. In the following example, I am assuming that $DEST is the existing directory in which the newly copied directory will appear. (You cannot call Get-Item on non-existant objects.)
$SOURCE = Get-Item "foo"
$DEST = Get-Item "bar"
Copy-Item $SOURCE $DEST -Filter {PSIsContainer} -Recurse -Force -PassThru | Where-Object {
# Get the parent object. The required member is different between
# files and directories, which makes this a bit more complex than it
# might have been.
if ($_.GetType().Name -eq "DirectoryInfo") {
$directory = $_.Parent
} else {
$directory = $_.Directory
}
# Select objects as required, in this case only allow through
# objects where the second level parent is the pre-existing target
# directory.
$directory.Parent.FullName -eq $DEST.FullName
}
Count the number of backslashes in the path and add logic to select first level only perhaps. Something like this perhaps?
$Dirs=get-childitem $Source -Recurse | ?{$_.PSIsContainer}
Foreach ($Dir in $Dirs){
$Level=([regex]::Match($Dir.FullName,"'b")).count
if ($Level -eq 1){Copy-Item $Dir $DEST -Force -Verbose}
else{Copy-Item $Dir $DEST -Force}}
*Edited to include looping and logic per requirements
I would suggest using robocopy instead of copy-item. Its /LEV:n switch sounds like it's exactly what you're looking for. Example (you'll need to test & tweak to meet your requirements):
robocopy $source $dest /LEV:2
robocopy has approximately 7 gazillion options you can specify to get some very useful and interesting behavior out of it.

Powershell script for comparing two directories based on file names only

I have two disks which has the same directory structure. C:\Files and D:\Files
Both C:\Files and D:\Files have multiple directories under them but have the same name etc, but the files inside them differ in extension. In C:\Files they are *.csv and in D:\Files, a process monitors the files (copied from C:\) and once it is done changes the files to *.processed.
I want a script that would do that copy. I.e copy files from C:\Files to D:\Files which have not been processed by comparing only the file names.
You want something like this. The property you want to compare on is called BaseName which powershell helpfully adds for you to the IO.FileSystemInfo class (FileInfo.BaseName exists in PowerShell but not in straight .NET). BaseName is just the name of the file, and doesn't contain any of the extensions that you don't care about.
$sourceFiles = Get-ChildItem C:\files -Recurse -File -Filter "*.csv"
$destinationFiles = Get-ChildItem D:\files -Recurse -File
foreach($sourceFile in $sourceFiles) {
$exists = $destinationFiles | Where-Object {$_.BaseName -eq $sourceFile.BaseName}
if(!$exists) {
Copy-Item $sourceFile.fullname -Destination "D:\Files\$($sourceFile.BaseName)"
}
}
dir .\ *csv -Recurse -File | cp -ea Ignore -WhatIf -Destination {
$dest=$_.FullName-replace'^C:','D:'-replace'\.csv','.processed'
if(Test-Path $dest){
Write-Host Already exists -ForegroundColor Yellow
$null
}else{
Write-Host Copying... -ForegroundColor Green
$dest-replace'\.processed$','.csv'
}
}
Notice the WhatIf parameter: you must remove it, if you're going to really copy the items.
Also, you may like to remove the 2 lines withwrite-host cmdlet.
Notive too, that I have hard-coded the C: and D: drives as source and destine drives.