Prioritize -Exclude over -Include in Get-ChildItem - powershell

I am trying to sanitize my source code into another folder using Powershell:
dir $sourceDir\* -Recurse -Exclude */bin/*,*/obj/* -Include *.sln, *.myapp, *.vb, *.resx, *.settings, *.vbproj, *.ico, *.xml
And it seems like everything is working fine, however, -Include directive sort of whitelists the file before -Exclude, so .XML files under /bin/, for example, are included. I would like -Exclude to take precedence over -Include, so always exclude /bin/ and /obj/ folders in the above script.
It is possible in Powershell, without writing too much code?

You can switch to late filtering to exclude the directories you don't want:
dir $sourceDir\* -Recurse -Include *.sln, *.myapp, *.vb, *.resx, *.settings, *.vbproj, *.ico, *.xml |
where {$_.fullname -notmatch '\\bin\\|\\obj\\'}
Using -like instead of -match:
dir $sourceDir\* -Recurse -Include *.sln, *.myapp, *.vb, *.resx, *.settings, *.vbproj, *.ico, *.xml |
where { ($_.fullname -notlike '*\bin\*') -and ($_.fullname -notlike '*\obj\*') }

Here is my take on it:
param(
$sourceDir="x:\Source",
$targetDir="x:\Target"
)
function like($str,$patterns){
foreach($pattern in $patterns) { if($str -like $pattern) { return $true; } }
return $false;
}
$exclude = #(
"*\bin\*",
"*\obj\*"
);
$include = #(
"*.sln",
"*.myapp",
"*.vb",
"*.resx",
"*.settings",
"*.vbproj",
"*.ico",
"*.xml"
);
dir $sourceDir\* -Recurse -Include $include | where {
!(like $_.fullname $exclude)
}
May not be very Powershell-ish, but it works. I used like function from here.
Any shorter answers are welcome - please go ahead and suggest an alternative.

Related

How to rename multiple files in powershell from parent folder?

Hello I would like to know is there a way to replace multiple filenames from parent folder?
I was able to rename multiple files using command below, but I have to access each folder first to rename multiple files.
dir .\* -include ('*.mp4','*.srt') | Rename-Item -NewName { $_.Name -replace '\[','' -replace '\]','' }
I was trying to replace dir .\* to dir .\**\* to select from parent folder but didn't work.
What am I missing?
You can do that by adding the -Recurse switch to Get-ChildItem (dir is alias to Get-ChildItem).
When searching for files that contain square brackets, you need to use the -LiteralPath parameter.
Also, be aware that there is a snag if you pipe the results from that directly to Rename-Item..
When doing so, Get-ChildItem may pick up the already processed files again, wasting time of course, so to prevent that you can either do:
$rootFolder = 'X:\WhereTheFilesAre'
(Get-ChildItem -LiteralPath $rootFolder -Include '*.mp4','*.srt' -File -Recurse) |
Rename-Item -NewName { $_.Name -replace '\[|]' }
or
$rootFolder = 'X:\WhereTheFilesAre'
$files = Get-ChildItem -LiteralPath $rootFolder -Include '*.mp4','*.srt' -File -Recurse
foreach ($file in $files) {
$file | Rename-Item -NewName { $file.Name -replace '\[|]' }
}
In additional to marsze's concise answer and realizing this isn't really a performance question:
-Include is much slower than -Filter because it filters after retrieving from the file system. Whereas, -Filter has the file system do the heavy lifting. In essence -Filter is a nuanced version of moving filtering operations left in the command/pipeline to improve performance. However, -Filter doesn't take multiple values! That said, you may still be able to exploit this characteristic using a loop, something like:
'*.mp4','*.srt' |
ForEach-Object{
Get-ChildItem .\ -Filter $_ -File
} |
Rename-Item -NewName { $_.Name -replace '\[|\]' }
-File works even though you are using -Recurse and may carry a modest performance improvement. I also shortened the -replace to 1 operation which can only help further.
What you probably want is the -Recurse switch:
dir "the folder" -Recurse -Include ('*.mp4','*.srt')
Note that this will recurse all levels of subdirectories.
I just found out I could use dir . to select all files (with specific file types), including files in subdirectories.
dir . -Recurse -Include ('*.srt', '*.mp4') | Rename-Item -NewName { $_.Name -replace '\[|\]','' }

How to include folders in Powershell -include string

I am doing some batch file name updates and am having trouble including folders. I have it currently set to target only specific file types, but I also want to include folders. Since folders don't have an extension I am unsure how to specify folders in the "-include" string. Any help would be greatly appreciated.
Here is what I am currently working with, but it ignores folders when I would like them included.
Get-ChildItem k:/toolbox/powershell -Include *.gif, *.jpg, *.png, *.xls,
*.xlsx, *.ppt, *.pptx, *.doc, *.docx, *.pdf -recurse | where {$_.name -match
"_"} | foreach {
$New=$_.name.Replace("_","-")
Rename-Item -path $_.Fullname -newname $New -passthru
}
(Get-Item '~\Desktop\*') | foreach { $FolderName = $_.name.Replace("_","-"); Rename-Item -path $_.fullname -newname $FolderName -passthru }
I would just add 2nd statement:
Get-ChildItem "k:/toolbox/powershell" -Recurse |where mode -eq d----- | where {$_.name -match "_"} | foreach {$New=$_.name.Replace("_","-")
Rename-Item -path $_.Fullname -newname $New -passthru }
If you run Get-ChildItem "k:/toolbox/powershell" you will receive a list of items where the "Mode" is "d-----" for directories, hence you can use that filter criteria for directories

How to move files from specific folders only using powershell

My code below moves databases from one location to another location:
$filters = Get-Content "c:\customerName.txt"
$source = "\\Server1\Databases"
$destination = "\\Server2\Databases"
foreach($filter in $filters)
{
Get-Childitem $source -include *.* -Recurse`
| ? {!$_.PsIsContainer -and $_.fullname -match $filter} `
| % {
Move-Item $_.FullName -Destination $destination"\$filter"
}
}
The code works absolutely fine, but I need to change it so that it doesnt move files from a specific folder i.e. \\Server1\Databases\AMG
So am trying to edit the above code as following:
foreach($filter in $filters)
{
Get-Childitem $source -include *.* -Recurse | where {$_.source -notlike *"\\Server1\Databases\AMG"*} `
| ? {!$_.PsIsContainer -and $_.fullname -match $filter} `
| % {
Move-Item $_.FullName -Destination $destination"\$filter"
}
}
But if I run the code, it moves everything including the stuff from \\Server1\Databases\AMG
How can I fix this code to work as it is supposed to? Any ideas?
You need to change:
Get-Childitem $source -include *.* -Recurse | where {$_.source -notlike *"\\Server1\Databases\AMG"*}
To:
Get-Childitem $source -include *.* -Recurse | where {$_.fullname -notlike "*\\Server1\Databases\AMG*"}
There is no .source property returned by Get-Childitem. Instead you could use .fullname which is the full path for each file or folder.
You should put the * wildcard characters inside the quote marks.

Recursively deleting files within folders without prompting the user via Powershell

I'm currently trying to remove all the files within a folder named Local. The script below does this, but it prompts the user in a PowerShell window. If I append the -Force flag my script then removes all files within all folders. I'm not sure how to remove all the files within the Local folder and not prompt the user. Below is the script in question.
$Path = "C:\Program Files (x86)\folder1\folder2\"
Function Clear-Cache
{
Get-ChildItem 'C:\Users\Admin\Documents\GI Studies' -File -Recurse | Where-Object {$_.FullName -match "data\\local"} | % {del $_.FullName #-Force}
}
Clear-Cache
You could first do a recursive search for the directory(ies) from which to delete files, then do a non-recursive delete of the files in each of those.
Function Clear-Cache
{
$localFolders = Get-ChildItem 'C:\Users\Admin\Documents\GI Studies' -Directory -Recurse | Where-Object {$_.FullName -match 'data\\local$'}
$localFolders |% { dir $_.FullName -File | del -Force }
}
Edit
Use FullName of directory when searching for files
Function Clear-Cache
{
$localFolders = Get-ChildItem 'C:\Users\Admin\Documents\GI Studies' -Directory -Recurse | Where-Object {$_.FullName -match 'data\\local$'}
$localFolders |% { dir $_.Fullname -File | del -Force }
}
Appending .FullName to the fifth line of your suggested code actually solved my problem. Thank you latkin for assisting me!
You can also use .NET Delete() function, it does not ask for confirmation:
$Path = "C:\folder"
$exclude = "file1.txt","file2.txt"
Get-ChildItem -Path $Path -Include * -Exclude $exclude -Recurse | foreach {
$_.Delete()
}

How to write a PowerShell function to get directories?

Using PowerShell I can get the directories with the following command:
Get-ChildItem -Path $path -Include "obj" -Recurse | `
Where-Object { $_.PSIsContainer }
I would prefer to write a function so the command is more readable. For example:
Get-Directories -Path "Projects" -Include "obj" -Recurse
And the following function does exactly that except for handling -Recurse elegantly:
Function Get-Directories([string] $path, [string] $include, [boolean] $recurse)
{
if ($recurse)
{
Get-ChildItem -Path $path -Include $include -Recurse | `
Where-Object { $_.PSIsContainer }
}
else
{
Get-ChildItem -Path $path -Include $include | `
Where-Object { $_.PSIsContainer }
}
}
How can I remove the if statement from my Get-Directories function or is this a better way to do it?
Try this:
# nouns should be singular unless results are guaranteed to be plural.
# arguments have been changed to match cmdlet parameter types
Function Get-Directory([string[]]$path, [string[]]$include, [switch]$recurse)
{
Get-ChildItem -Path $path -Include $include -Recurse:$recurse | `
Where-Object { $_.PSIsContainer }
}
This works because -Recurse:$false is the same has not having -Recurse at all.
In PowerShell 3.0, it is baked in with -File -Directory switches:
dir -Directory #List only directories
dir -File #List only files
The answer Oisin gives is spot on. I just wanted to add that this is skirting close to wanting to be a proxy function. If you have the PowerShell Community Extensions 2.0 installed, you already have this proxy function. You have to enable it (it is disabled by default). Just edit the Pscx.UserPreferences.ps1 file and change this line so it is set to $true as shown below:
GetChildItem = $true # Adds ContainerOnly and LeafOnly parameters
# but doesn't handle dynamic params yet.
Note the limitation regarding dynamic parameters. Now when you import PSCX do it like so:
Import-Module Pscx -Arg [path to Pscx.UserPreferences.ps1]
Now you can do this:
Get-ChildItem . -r Bin -ContainerOnly