I have to filter my results by certain strings and tried to do it with -match and -contains.
-match works if I have just one value to filter, but not with an array.
-contains neither works with one string, nor with a string array.
Why isn't it working with several values? Especially the -contains. Or is there another easy way to solve it?
$Folder = 'C:\Test'
$filterArray = #('2017-05', '2017-08')
$filter = '2017-05'
## test with -MATCH
## working with one match string
Get-ChildItem -Path $Folder -Recurse -Include *.txt |
Where { $_.FullName -match $filter } |
ForEach-Object { $_.FullName }
## NOT working with match string array - no results
Get-ChildItem -Path $Folder -Recurse -Include *.txt |
Where { $_.FullName -match $filterArray } |
ForEach-Object { $_.FullName }
## test with -CONTAINS
## NOT working with one contains string - no results
Get-ChildItem -Path $Folder -Recurse -Include *.txt |
Where { $_.FullName -contains $filter } |
ForEach-Object { $_.FullName }
## NOT working with contains string array- no results
Get-ChildItem -Path $Folder -Recurse -Include *.txt |
Where { $_.FullName -contains $filterArray } |
ForEach-Object { $_.FullName }
Using an array as the second operand for the -match or -contains operators doesn't work. There are basically two approaches you could take:
Build a regular expression from the array and use that with the -match operator:
$pattern = #($filterArray | ForEach-Object {[regex]::Escape($_)}) -join '|'
... | Where-Object { $_.FullName -match $pattern }
This is the preferred approach.
Use a nested Where-Object filter and the String.Contains() method:
... | Where-Object {
$f = $_.FullName
$filterArray | Where-Object { $f.Contains($_) }
}
Why isn't it working with several values?
Because these operators were designed to test against a single argument, plain and simple.
The ability to match against multiple arguments in a single operation would beg the question: "Does the input need to satisfy all or any of the argument conditions"?
If you want to test for a match against any of an array of regex patterns, you can construct a single pattern from them using a non-capturing group, like so:
$filterPattern = '(?:{0})' -f ($filterArray -join '|')
Get-ChildItem -Path $Folder -Recurse -Include *.txt | Where {$_.FullName -match $filterPattern} | ForEach-Object{ $_.FullName }
You can also drop the Where-Object and ForEach-Object loop completely, since PowerShell 3.0 supports property enumeration:
(Get-ChildItem -Path $Folder -Recurse -Include *.txt).FullName -match $filterPattern
Related
cd 'A:\P\E\D'
$files = Get-ChildItem . *.CSV -rec
ForEach ($file in $files) {
(Get-Content $file -Raw) | ForEach-Object {
*some simple code*
} | Set-Content $file
}
How to modify this powershell script to locate only files starting with letters A/a to O/o and ending with .csv in specified directory cd?
I thought the solution below would work, but the test file M_K_O_X.CSV stored in the cd directory was not found and modified. The solution above will find and modify the file. It's possible that I have the regex expression wrong or the problem is somewhere else? I tried also this regex -- "[A-O]..CSV"
cd 'A:\P\E\D'
$files = Get-ChildItem . -rec | Where-Object { $_.Name -like "[a-oA-O]*.*.CSV" }
ForEach ($file in $files) {
(Get-Content $file -Raw) | ForEach-Object {
*some simple code*
} | Set-Content $file
}
Looking at your wildcard pattern, seems like you have an extra *. that shouldn't be there:
'M_K_O_X.CSV' -like '[a-oA-O]*.*.CSV' # False
'M_K_O_X.CSV' -like '[a-oA-O]*.CSV' # True
In this case you could simply use the -Include Parameter which supports character ranges. Also PowerShell is case insensitive by default, [a-oA-O]*.CSV can be reduced to [a-o]*.CSV:
Get-ChildItem 'A:\P\E\D' -Recurse -Include '[a-o]*.csv' | ForEach-Object {
($_ | Get-Content -Raw) | ForEach-Object {
# *some simple code*
} | Set-Content -LiteralPath $_.FullName
}
As commented, I would use the standard wildcard -Filter to filter for all files with a .csv extension.
Then pipe to a Where-Object clause in which you can use regex -match
$files = Get-ChildItem -Path 'A:\P\E\D' -Filter '*.csv' -File -Recurse |
Where-Object { $_.Name -match '^[a-o]' }
foreach ($file in $files) {
# switch `-Raw` makes Get-Content return a single multiline string, so no need for a loop
$content = Get-Content -Path $file.FullName -Raw
# *some simple code manipulating $content*
$content | Set-Content -Path $file.FullName
}
However, if these are valid csv files, I would not recommend using a pure textual manipulation on them, instead use Import-Csv -Path $file.FullName and work on the properties on each of the objects returned.
So, i've been scratching my head for a while now and can't seem to figure it out.
I want to delete files and folders older than 'x' days <-- this works fine
I want to delete empty directories left behind <-- this works fine as well
I also want to have some exceptions: filenames and foldernames. The filename exception works fine, but folders don't. There is something strange though. If i put only 1 name in the array of folders i don't want to delete, it works just fine. But if i put multiple in, it suddenly doesn't work anymore?
I have the idea it might be something simple i'm completely missing
$limit = (Get-Date).AddDays(-120)
$path = "C:\Users\user\Documents\files"
$ExcludedFileNames = #("*file1*", "*file2*")
$ExcludedFolders = #("*folder1*", "*folder2*")
# Delete files older than the $limit.
Get-ChildItem -Path $path -Recurse -Force -exclude $ExcludedFileNames |
Where-Object {($_.FullName -notlike $ExcludedFolders) -and (!$_.PSIsContainer) -and ($_.LastWriteTime -lt $limit) } |
Remove-Item -Force
# Delete any empty directories left behind after deleting the old files.
Get-ChildItem -Path $path -Recurse -Force | Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } | Remove-Item -Force -Recurse
Instead of $.FullName i tried $.Name
Instead of -notlike i tried -notin
I also tried removing the array and put the variables after where-object
I also tried to copy other code from lots of posts but didn't seem to help.
The problem is that -notlike expects a single string as it's right-hand side operand, and so the $ExcludedFolders variable is coerced into the stringvalue "*folder1* *folder2*".
The comparison 'C:\some\path\to\a\folder1\with\a\file.exe' -notlike '*folder1* *folder2*' obviously fails.
You can solve this by using the -notmatch regex operator instead:
$ExcludedFolders = #('folder1', 'folder2') # note that we no longer need the wildcards
# later
... |Where-Object {$_.FullName -notmatch ($ExcludedFolders.ForEach{[regex]::Escape($_)} -join '|') -and (-not $_.PsIsContainer) -and $_.LastWriteTime -lt $limit}
The | is the alternation operator in regex, effectively functioning as an OR
I would use wildcards on the file names to use with the -Exclude parameter, and create a regex string for the foldernames to exclude you can use in the Where-Object clause.
Something like this:
$limit = (Get-Date).AddDays(-120).Date # set to midnight instead of the current time
$path = 'C:\Users\user\Documents\files'
$ExcludedFileNames = '*file1*', '*file2*' # wildcards for the Exclude parameter
$ExcludedFolders = 'folder1','folder2' # can be a partial name, do not use wildcards here
# create a regex string for the folder names to exclude
# each item will be Regex Escaped and joined together with the OR symbol '|'
$FoldersToSkip = ($ExcludedFolders | ForEach-Object { [Regex]::Escape($_) }) -join '|'
# Delete files older than the $limit.
Get-ChildItem -Path $path -File -Recurse -Force -Exclude $ExcludedFileNames |
Where-Object {($_.DirectoryName -notmatch $FoldersToSkip) -and ($_.LastWriteTime -lt $limit) } |
Remove-Item -Force
# Delete any empty directories left behind after deleting the old files.
(Get-ChildItem -Path $path -Recurse -Directory -Force).FullName |
Where-Object { !( Get-ChildItem -Path $_ | Select-Object -First 1 ) } |
Sort-Object -Property Length -Descending |
Remove-Item -Force
Here what I want to do is get the list of the folders that have files which has the value of ErrorCode > 0.
This is what I have done till now.
$fileNames = Get-ChildItem -Path $scriptPath -Recurse -Include *.data
$FoldersToRename = #() #initialize as array
foreach ($file in $fileNames) {
If (Get-Content $file | %{$_ -match '"ErrorCode": 0'})
{
echo "matched"
}
Now I have .data file which are being searched by this program. It contains an object with a value of "ErrorCode":value. I want to perform some operations only if that value is greater than zero.
How do I solve this?
One way to do it is like this:
Get-ChildItem -Path $scriptPath -Filter *.data |
ForEach-Object {
if((Get-Content -Path $_.FullName -Raw) -match '"ErrorCode": [1-9]\d*') {
"Matched"
}
}
I have written a script that will recurse a specified folder and do some analysis on the files within it. I need to exclude specified sub-folders in the analysis. This list of exclusions changes dependent on the base folder being analysed. I have the script working using a long pattern like this:
Get-ChildItem -File -Recurse $source_folder |
Where-Object {
$_.FullName -notlike "*\folder_name0\*" -and
$_.FullName -notlike "*\folder_name1\*" -and
$_.FullName -notlike "*\folder_name2\*" -and
$_.FullName -notlike "*\folder_name3\*" -and
$_.FullName -notlike "*\folder_name4\*"
}
but this is not very reusable. I would like to be able to store exception lists in .CSVs and call the exception list I need based on the folder set I am analyzing. What I would like to do is something like:
$exception_list = Import-CSV .\exception_list
Get-ChildItem -File -Recurse $source_folder |
Where-Object {$_.FullName -notlike $exception_list}
but this does not work. I suspect because I can't specify and 'and' or an 'or' between the elements in the array. I did briefly consider trying to create the whole argument on the fly using a foreach($exception in $exception_list){$argument += "$_.FullName -notlike $exception -and"}, but that got silly and complex pretty quickly since you still have to remove the last 'and'.
Is there an efficient way to do this?
this builds an array of partial names to be excluded, and uses that array to build a regex OR for use in a -notmatch test.
$ExcludedDirList = #(
'PSES-'
'vscode'
'Test_'
)
# regex uses the pipe symbol as the logical "OR"
$RegexExcludedDirList = $ExcludedDirList -join '|'
$Results = Get-ChildItem -Path $env:TEMP -File -Recurse |
Where-Object {
$_.DirectoryName -notmatch $RegexExcludedDirList
}
I really like #lee_dailey's pattern of creating the regex. An alternative method could be to use -in or -notin to compare collections.
Using Pester:
It 'Filters correctly' {
$list = #('fileA', 'file1', 'file2', 'file32')
$filter = #('file1', 'file3')
$expected = #('fileA', 'file2', 'file32')
$list | Where-Object { $_ -notin $filter} | should -be $expected
}
Or just plain comparison operators:
$list = #('fileA', 'file1', 'file2', 'file32')
$filter = #('file1', 'file3')
$expected = #('fileA', 'file2', 'file32')
$newlist = $list | Where-Object { $_ -notin $filter}
(Compare-Object $newlist $expected).length -eq 0
> True
I am trying to recursively remove certain characters from files and folders using a PowerShell script. Below is the script that I have found, but it will only remove underscores from files, not folders. There are a few characters which will need to be removed, but I am fine with having a script for each character if need be. It does recursively change files in the folders, but no folders are 'fixed'.
Current PowerShell script:
'dir -Recurse | where {-not $_.PsIscontainer -AND $_.name -match "_"} | foreach {
$New = $_.name.Replace("_","")
Rename-Item -path $_.Fullname -newname $New -passthru
}'
As pointed out in the comments the core of your issue is that you are excluding folders with the -Not $_.PsIscontainer component of your Where block.
dir -recurse | where {$_.name -match "_"} | ...
The second issue that you are having is most likely that since you are changing folder names the children you had previously inventoried with dir/Get-ChildItem would then have incorrect paths. One way to address this would be to process files first then folders.
$filesandfolders = Get-ChildItem -recurse | Where-Object {$_.name -match "_"}
$filesandfolders | Where-Object {!$_.PsIscontainer} | foreach {
$New=$_.name.Replace("_","")
Rename-Item -path $_.Fullname -newname $New -passthru -WhatIf
}
$filesandfolders | Where-Object {$_.PsIscontainer} | foreach {
$New=$_.name.Replace("_","")
Rename-Item -path $_.Fullname -newname $New -passthru -WhatIf
}
By no means the prettiest solution but it would work. It processes all the files first, then the folders. Remove the -Whatifs when you are sure it would do what you expect
Other characters
You had mentioned there were other characters that you were looking to remove. That wouldn't be a tall order. You could be using regex for this so lets try that.
$characters = "._"
$regex = "[$([regex]::Escape($characters))]"
$filesandfolders = Get-ChildItem -recurse | Where-Object {$_.name -match $regex}
$filesandfolders | Where-Object {!$_.PsIscontainer} | foreach {
$New=$_.name -Replace $regex
Rename-Item -path $_.Fullname -newname $New -passthru -WhatIf
}
$filesandfolders | Where-Object {$_.PsIscontainer} | foreach {
$New=$_.name -Replace $regex
Rename-Item -path $_.Fullname -newname $New -passthru -WhatIf
}
That would remove all of the periods and underscores from those files and folders.
Not tested but you might even be able to get it down to these few lines
$characters = "._"
$regex = "[$([regex]::Escape($characters))]"
$filesandfolders = Get-ChildItem -recurse | Where-Object {$_.name -match $regex}
$filesandfolders | Where-Object {!$_.PsIscontainer} | Rename-Item -NewName ($_.name -Replace $regex) -PassThru -WhatIf
$filesandfolders | Where-Object {$_.PsIscontainer} | Rename-Item -NewName ($_.name -Replace $regex) -PassThru -WhatIf