How to write a PowerShell function to get directories? - powershell

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

Related

How to move files based on filename using PowerShell?

I am new to PowerShell and am stuck at something.
How to move files based on filename using PowerShell?
Move 0001_ddmmyy_username[active1]_L_kkkkk.pdf to C:\Users\abc\London
Move 0001_ddmmyy_jacky[active1]_R_kkkkk.pdf to C:\Users\abc\Russia
Move 0001_ddmmyy_jim[active1]_P_kkkkk.pdf to C:\Users\abc\Poland
I used the following code to move files to a folder called London. It fails if the file name has any square bracket with text [kkkk].
gci 'C:\Users\abc' -Recurse -Include "*_L*" | %{ Move-Item $_.FullName 'C:\Users\abc\London'}
How can I automate this?
There are multiple ways:
Get-Location
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-location?view=powershell-7
gci (Get-Location).Path
or simply .\
gci .\
additionaly, gci will default to current path if none is specified. So the following will get the current folder:
gci
Use -LiteralPath in the Move-Item cmdlet.
Move-Item -LiteralPath $sourcePath $destinationPath
I'd do something similar to below:
# Note I'm using -File so it only returns files
$items = gci .\ -Recurse -File -Include *_L*, *_R*, *_P*
foreach($item in $items) {
# Could utilize something cleaner/better but this will do
if($item.Name -like "*_L*") {
Move-Item -LiteralPath $item 'C:\Users\abc\London'
} elseif($item.Name -like "*_R*") {
Move-Item -LiteralPath $item 'C:\Users\abc\Russia'
} elseif ($item.Name -like "*_P*") {
Move-Item -LiteralPath $item 'C:\Users\abc\Poland'
} else {
# Unexpected output
Write-Host "The path did not match what was expected"
Write-Host $item.Name
}
}
Windows 10 64-bit. PowerShell 5.
How to move files based on file basename using PowerShell gci, foreach, if, -like, move-item and -literalpath
Move 0001_ddmmyy_username[active1]_L_kkkkk.pdf to C:\Users\abc\London
Move 0001_ddmmyy_jacky[active1]_R_kkkkk.pdf to C:\Users\abc\Russia
Move 0001_ddmmyy_jim[active1]_P_kkkkk.pdf to C:\Users\abc\Poland
Change $env:userprofile\desktop. Remove -whatif when you are satisfied it works.
$items = gci -recurse -file *_L_*, *_R_*, *_P_*
foreach($item in $items) {
# Change $env:userprofile\desktop
if($item -like "*_L_*") {
Move-Item -LiteralPath $item "$env:userprofile\desktop\London" -whatif
} elseif($item -like "*_R_*") {
Move-Item -LiteralPath $item "$env:userprofile\desktop\Russia" -whatif
} elseif ($item -like '*_P_*') {
Move-Item -LiteralPath $item "$env:userprofile\desktop\Poland" -whatif
}
}
Comments:
With some cmdlets you will get odd results when you use -Path and any part of the full file name contains []. The fix is to use the -LiteralPath parameter. See Get-Help Move-Item -Parameter *path* – Lee_Dailey
How to move files based on file basename using PowerShell gci, foreach, if, -like, move-item and -literalpath

Powershell v5 Get-ChildItem -File not working?

Get-ChildItem -Path $dir -Recurse -File | ForEach-Object `
{
Write-Host $_.fullname
}
Get-ChildItem : A parameter cannot be found that matches
parameter name 'File'.
Version
-------
5.1.17763.771
I have many other ps scripts running that use this and it works fine, but for some reason it's not working in this new script I wrote. What's going on?
Thank you everyone for helping.
The value of $dir was
" S:\folder\folder\"
Removing the space before the drive letter resolved the issue.
In addition to the OP's particular instance, there are other cases where this error can occur. Get-ChildItem can be used on other providers besides the file provider (e.g., the registry), and the -File switch is not valid in those cases.
Example:
$dir = 'C:\Temp'
Get-ChildItem -Path $dir -File # Works just fine
$dir = 'HKCU:\Software'
Get-ChildItem -Path $dir # Works without -File switch
Get-ChildItem -Path $dir -File # Throws "parameter cannot be found" error
That wasn't the source of the problem for me.
All I did was surround the variable with ( ) and that resolve it.
In my case I wanted the full filename of the 'found' file
$curdir = "C:\Program Files\"
(Get-Childitem -Path ($curdir) -File -filter "node.exe" -recurse -Force -ErrorAction SilentlyContinue).Directory.FullName
In your case I'm sure just using this would work for you:
Get-ChildItem -Path ($dir) -Recurse -File | ForEach-Object `
{
Write-Host $_.fullname
}

Is there a way to set a path using regular expression?

I'm using PowerShell command prompt and I want to set a location to the folder "Folder". The thing is that folder can be everywhere.
I've tried the command Set-Location and Resolve-Path but I don't get it.
I want to do something like that:
$path = Resolve-Path ".*/Folder"
Set-Location $path
Where the .* can be all the parent folder
Any idea?
Would try this:
Get-ChildItem -Path .\ -Name Folder -Recurse -Depth 10
Hope it helps. BR
Edit (see comments):
$array = Get-ChildItem -Path .\ -Name Ping -Recurse -Depth 10
if($array.Count -eq 0){
#stay
}
if($array.Count -eq 1){
Set-Location $array
}
else{
$array | ForEach-Object{Write-Host $_}
}
Requires PowerShell v3 or higher, you can check using $PSVersionTable.*
$path = (Get-ChildItem . -Recurse -Directory | Where-Object { $_.Name -eq "Folder" }).FullName
This will give you all the directories named Folder in current directory and its subdirectories recursively.
The query, however, can return multiple results so if you want to choose the first one use [0]. Also, to cover the case when the query returns no results, enforce returned object to be an array using #( ... ) and check if it exists:
$f = #(Get-ChildItem . -Recurse -Directory | Where-Object { $_.Name -eq "Folder" })[0].FullName
if ( $f ) {
cd $f
}
else {
# Handle the error
}
cd is alias to Set-Location so you can use whichever you prefer. Important thing to remember is to use FullName property as it contains full path to the folder.
* For PowerShell versions lower than v3, use | Where-Object {$_.PSIsContainer -eq $true} instead of -Directory

Prioritize -Exclude over -Include in Get-ChildItem

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.

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()
}