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.
Related
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.
I am newbie to powershell scripting and I did go through lot of articles here and not able to get much help.Can someone help me out on how to put files in specific folders:
file name example:
2008_11_chan_3748_NB001052_031_SIGNED.pdf
put it in folders:
2008/11/NB001052-031/....
if it ends with "draft", e.g.
2008_11_chan_3748_NB001052_031_Draft.pdf
put it in a separate draft folder as below
2008/11/NB001052-031/Draft
Any help is really appreciated.
Thanks in advance.
Push-Location c:\2009
Get-ChildItem -File -Filter *_*_*_*_*_*.pdf |
Move-Item -Destination {
$tokens = $_.Name -split '_'
$subdirNames = $tokens[0,1,4]
if ($tokens[-1] -like 'Draft.*') { $subdirNames += 'Draft' }
(New-Item -Force -Type Directory ($subdirNames -join '\')).FullName
} -WhatIf
Pop-Location
Note: The -WhatIf common parameter in the command above previews the move operation, but the target subfolders are created right away in this case.
Remove -WhatIf once you're sure the operation will do what you want.
(Partial) explanation:
The Get-ChildItem call gets those files in the current directory whose name matches wildcard pattern *_*_*_*_*_*.pdf.
Move-Item -Destination uses a delay-bind script block ({ ... }) to dynamically determine the target path based on the current input object, $_, which is of type System.IO.FileInfo
The code inside the script block uses the -split operator to split the file name into tokens by _, extracts the tokens of interest and appends Draft as appropriate, then creates / returns a subdirectory path based on the \-joined tokens joined in order to output the target directory path; note that -Force creates the directory on demand and quietly returns an existing directory.
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
}
I have powershell scripts running on a remote server that delete files older than 30 days stored on shared drives using automated tasks. We have excluded this folder with a specific name from deletion which is usually found in the root directory of the shared drives. However in one of the shared drives, this folder is found one level below the root level, and there are seven such subfolders. I want to exclude this folder with the specific name from deletion in all these subfolders with a single script. Is there a way to do this? Here is the script I am currently using-
Get-Item -Path $Delete_Global_Test -Force -exclude $Keep_this_folder,$Keep_this_folder2|Get-ChildItem -Recurse|Where-Object {!$_.PSIsContainer -and $_.CreationTime -lt $regional_limit}|Remove-Item -Recurse -Force -Verbose
adding -Exclude "notthisfolder" would be my solution
If you know the directory, and the directory name never changes, why don't you just create a secondary path variable that drills down into that directory and run the script again?
So if this works for you (your code), why not just create a second path variable (e.g. $Delete_Global_Test2) which drills down to the directory that's one deeper and rerun the same command on a new line, but using your second path variable?
Your script would work like this:
Get-Item -Path $Delete_Global_Test -Force -exclude $Keep_this_folder,$Keep_this_folder2|Get-ChildItem -Recurse|Where-Object {!$_.PSIsContainer -and $_.CreationTime -lt $regional_limit}|Remove-Item -Recurse -Force -Verbose
#Now run again but in a second root path
Get-Item -Path $Delete_Global_Test2 -Force -exclude $Keep_this_folder,$Keep_this_folder2|Get-ChildItem -Recurse|Where-Object {!$_.PSIsContainer -and $_.CreationTime -lt $regional_limit}|Remove-Item -Recurse -Force -Verbose
And all you'd have to add is the declaration for $Delete_Global_Test2, which would be the same as $Delete_Global_Test but with the additional subdirectory appended to the original.
Would that work for your purposes?
All,
My intention is to copy all the files with starting with the name 'US.Services' and with the extension .dll from a directory and its sub directories to the place where the script is being executed, i have the following but nothing gets copied. Any help would be appreciated.
Get-Childitem -Path ".\.\" -Filter *US.Services*.dll -Recurse |
Copy-Item -Destination "."
Thanks -Nen
Since PowerShell v3 can use the $PSScriptRoot automatic variable to refer to the location where the script is saved (in PowerShell v2 that would be $here = $MyInvocation.MyCommand.Path | Split-Path.
Be aware the both those approaches work only when the script is executed, if you just paste them to PowerShell console they won't return any value.
If I understand your question correctly you look for files that start with the given string and end with the extension, so you need to use the * wildcard here: US.Services*.dll.
Get-Childitem -Path $PSScriptRoot -Recurse -Filter "US.Services*.dll" |
Copy-Item -Destination $PSScriptRoot
This will likely produce exceptions if there are files with the same name copied to the single directory, as two files cannot be named the same within single directory.