Powershell Script to Get Files with Certain Date in String - powershell

I'm building a powershell script to only grab files with a certain date in the string (yesterday's date) and display their names. But it doesn't seem to be going well. I've tried Googling but haven't found specifically help on what I'm trying to do:
$a = (Get-Date).AddDays(-1).ToString('yyyyMMdd')
$b = Get-ChildItem "E:\Export" -Filter {$_.Name -like '*'+$a.ToString()}
Get-ChildItem "E:\Export" -Filter *.txt |
Foreach-Object {
If ($b -like $a)
{
Write-Host $b
}
}
Any help would be appreciated.

$a IS already a string. You can't simply put a script block as a filter.
$a = (Get-Date).AddDays(-1).ToString('yyyyMMdd')
$b = Get-ChildItem "E:\Export" | Where-Object BaseName -like "*$a*"
$b
or
$b = Get-ChildItem "E:\Export\*$a*"

td;dr
$b = Get-ChildItem "E:\Export" -Filter ('*' + $a)
Or, using PowerShell's string expansion (interpolation):
$b = Get-ChildItem "E:\Export" -Filter "*$a"
-Filter parameter values:
are always [string]-typed
their specific syntax is provider-dependent
Since you're dealing with files, it is the FileSystem PS provider that interprets -Filter, and it expects a wildcard expression as an argument, as accepted by the underlying Windows API; the wildcard expression is implicitly matched against the file name.
Note:
Typically - such as in this case - such wildcard expressions work the same as PowerShell's own wildcard expressions, but the former have quirks in order to support legacy applications, while the latter offer additional features.
No standard provider accepts script blocks with arbitrary PowerShell code as a -Filter arguments, despite their widespread - but misguided - use with the Active Directory provider - see this answer.
To perform arbitrary filtering of output objects via script blocks in PowerShell code, pipe to the Where-Object cmdlet, as shown in LotPings' answer.
However, if feasible, use of -Filter should always be the first choice, because it filters at the source, meaning that the provider returns the already-filtered results to PowerShell (as opposed to having to filter the results after the fact, in PowerShell code), which can greatly speed up operations.

Related

Powershell Folder List Filter

I have a folder and inside has a list of subfolders/files
Folders
2022
20221101
20221103
20221107
20221108
test123
results
test.txt
Using Powershell
How do get the list of folders that are dates.
How do I get the second latest folder (20221107).
This is what I was able to come with so far:
Get-ChildItem "C:\code\Test" -Filter "2022*" | Sort-Object Name -Descending
You can use TryParseExact method from DateTime to parse the folders names and avoid any type of error, this can be combined with Sort-Object and Select-Object to get the 2nd latest. I have added -Directory to output only folders. Also changed your filter to -Filter "2022????" to ensure you're matching folders that start with 2022 and are followed by 4 characters.
$ref = [ref] [datetime]::new(0)
Get-ChildItem "C:\code\Test" -Filter "2022????" -Directory | Sort-Object {
$result = [datetime]::TryParseExact(
$_.Name,
'yyyyMMdd',
[cultureinfo]::InvariantCulture,
[Globalization.DateTimeStyles]::AssumeLocal,
$ref
)
if($result) { $ref.Value }
} -Descending | Select-Object -Index 1
If you want to ensure you're matching folders with a date format (because -Filter may be too permissive), you can pipe to Where-Object for additional filtering via regex:
$ref = [ref] [datetime]::new(0)
Get-ChildItem "C:\code\Test" -Filter "2022????" -Directory |
Where-Object Name -Match '^2022\d{4}$' | Sort-Object {
# rest of the code here
} -Descending | Select-Object -Index 1
To provide an alternative to Santiago's helpful answer:
Since the timestamps that your folder names represent sort lexically in a way that is equivalent to their chronological sorting, you may not need to convert them to [datetime] instances, and can sort them as-is.
Get-Item C:\code\Test\* -Include 2022???? |
Sort-Object Name -Descending |
Select-Object -Index 1
Note the use of -Include instead of -Filter (which in turn necessitates ending the -Path argument with \* and using Get-Item instead of Get-ChildItem), because the -Filter parameter has legacy quirks that prevent character-exact matching with multiple ? wildcards - see this answer for background information.
Unfortunately, as this solution and the linked answer shows, making -Include (and -Exclude) work as intended is tricky as of PowerShell 7.2.x, and requires memorizing non-intuitive rules.
On the plus side, -Include, which (unlike -Filter) uses PowerShell's wildcard expressions, would also allow you to create a more specific pattern, such as -Include 2020[0-1][0-9][0-3][0-9] (which still isn't strict enough to rule out invalid digit combinations, however).

Script returning error: "Get-Content : An object at the specified path ... does not exist, or has been filtered by the -Include or -Exclude parameter

EDIT
I think I now know what the issue is - The copy numbers are not REALLY part of the filename. Therefore, when the array pulls it and then is used to get the match info, the file as it is in the array does not exist, only the file name with no copy number.
I tried writing a rename script but the same issue exists... only the few files I manually renamed (so they don't contain copy numbers) were renamed (successfully) by the script. All others are shown not to exist.
How can I get around this? I really do not want to manually work with 23000+ files. I am drawing a blank..
HELP PLEASE
I am trying to narrow down a folder full of emails (copies) with the same name "SCADA Alert.eml", "SCADA Alert[1].eml"...[23110], based on contents. And delete the emails from the folder that meet specific content criteria.
When I run it I keep getting the error in the subject line above. It only sees the first file and the rest it says do not exist...
The script reads through the folder, creates an array of names (does this correctly).
Then creates an variable, $email, and assigns the content of that file. for each $filename in the array.
(this is where is breaks)
Then is should match the specific string I am looking for to the content of the $email var and return true or false. If true I want it to remove the email, $filename, from the folder.
Thus narrowing down the email I have to review.
Any help here would be greatly appreciated.
This is what I have so far... (Folder is in the root of C:)
$array = Get-ChildItem -name -Path $FolderToRead #| Get-Content | Tee C:\Users\baudet\desktop\TargetFile.txt
Foreach ($FileName in $array){
$FileName # Check File
$email = Get-Content $FolderToRead\$FileName
$email # Check Content
$ContainsString = "False" # Set Var
$ContainsString # Verify Var
$ContainsString = %{$email -match "SYS$,ROC"} # Look for String
$ContainsString # Verify result of match
#if ($ContainsString -eq "True") {
#Remove-Item $FolderToRead\$element
#}
}
Here's a PowerShell-idiomatic solution that also resolves your original problems:
Get-ChildItem -File -LiteralPath $FolderToRead | Where-Object {
(Get-Content -Raw -LiteralPath $_.FullName) -match 'SYS\$,ROC'
} | Remove-Item -WhatIf
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.
Note how the $ character in the RHS regex of the -match operator is \-escaped in order to use it verbatim (rather than as metacharacter $, the end-of-input anchor).
Also, given that $ is also used in PowerShell's string interpolation, it's better to use '...' strings (single-quoted, verbatim strings) to represent regexes, assuming no actual up-front string expansion is needed before the regex engine sees the resulting string - see this answer for more information.
As for what you tried:
The error message stemmed from the fact that Get-Content $FolderToRead\$FileName binds the file-name argument, $FolderToRead\$FileName, implicitly (positionally) to Get-Content's -Path parameter, which expects PowerShell wildcard patterns.
Since your file names literally contain [ and ] characters, they are misinterpreted by the (implied) -Path parameter, which can be avoided by using the -LiteralPath parameter instead (which must be specified explicitly, as a named argument).
%{$email -match "SYS$,ROC"} is unnecessarily wrapped in a ForEach-Object call (% is a built-in alias); while that doesn't do any harm in this case, it adds unnecessary overhead;
$email -match "SYS$,ROC" is enough, though it needs to be corrected to
$email -match 'SYS\$,ROC', as explained above.
[System.IO.Directory]::EnumerateFiles($Folder) |
Where-Object {$true -eq [System.IO.File]::ReadAllText($_, [System.Text.Encoding]::UTF8).Contains('SYS$,ROC') } |
ForEach-Object {
Write-Host "Removing $($_)"
#[System.IO.File]::Delete($_)
}
Your mistakes:
%{$email -match "SYS$,ROC"} - What % is intended to be? This is ForEach-Object alias.
%{$email -match "SYS$,ROC"} - Why use -match? This is much slower than -like or String.Contains()
%{$email -match "SYS$,ROC"} - When using $ inside double quotes, you should escape this using single backtick symbol (I have `$100). Otherwise, everything after $ is variable name: Hello, $username; I's $($weather.ToString()) today!
Write debug output in a right way: use Write-Debug, Write-Verbose, Write-Host, Write-Warning, Write-Error, Write-Information.
Can be better:
Avoid using Get-ChildItem, because Get-ChildItem returns files with attributes (like mtime, atime, ctime, etc). This additional info is additional request per file. When you need only list of files, use native .Net EnumerateFiles from System.IO.Directory. This is significant performace boost on huge amounts of files.
Use RealAllText or ReadAllLines or ReadAllBytes from System.IO.File static class to be more concrete instead of using universal Get-Content.
Use pipelines ;-)

Using Get-ChildItem with a string variable

I'm using Get-ChildItem to save file locations that match user-input parameters. I can't seem to figure out (I'm new to powershell) how to use string variables with cmdlets.
I'll define $search_param as it is in my script below.
I've tried using the built in debugger on powershell ise, and if anything it's confused me even more.
This is where I found that replacing the use of '$search_param' with the actual value of $search_param produced a csv output, but using '$search_param' gives no output. Some useful information might be that I defined $search_param with double quotations.
The line that is causing all the issues is:
Get-ChildItem 'C:\Users\USER\' -recurse '$search_param'
$search_param is defined as:
| where { $_.name -like "*peanut*" -or $_.name -like "*butter*" -or $_.name -like "*test*"}
| where { $_.extension -in ".txt",".csv",".docx" }
| where { $_.CreationTime -ge "08/07/1998" -and $_.CreationTime -le "08/07/2019" }
| select FullName | Export-Csv -Path .\SearchResults.csv -NoTypeInformation
Funnily enough I had it all working before I went to lunch and came back to a different story..
I'm using Export-csv in another piece of my script and that is working as intended.
I may have touched something small that isn't related to the line I provided as I think this should be working..
Per Technician's comment:
You can use invoke expression to "evaluate or run a specified string as a command and return the results of the expression or command."
Invoke-Expression "Get-ChildItem 'C:\Users\SGouldin\' -recurse $search_param"
From a little bit of research, relying on invoke expression isn't always the best practice.
In my case, I needed it because of the way I was attempting to handle user input (some weeeeird string concatenation/joins).

Removing Parts of a File Name based on a Delimiter

I have various file names that are categorized in two different ways. They either just have a code like: "866655" or contain a suffix and prefix "eu_866655_001". My hope is to write to a text file the names of files in the same format. I cannot figure out a successful method for removing the suffix and prefix.
Currently this what I have in my loop in Powershell:
$docs = Get-ChildItem -Path $source | Where-Object {$_.Name -match '.doc*'}
if ($docs.basename -contians 'eu_*')
{
Write-Output ([io.fileinfo]"$doc").basename.split("_")
}
I'm hoping to turn "eu_866655_001" into "866655" by using "_" as the delimiter.
I'm aware that the answer is staring me down but I still can't seem to figure it out.
You could do something like the following. Feel free to tweak the -Filter on the Get-ChildItem command.
$source = 'c:\path\*'
$docs = Get-ChildItem -Path $source -File -Filter "*_*_*" -Include '*.doc','*.docx'
$docs | Rename-Item -NewName { "{0}{1}" -f $_.Basename.Split('_')[1],$_.Extension }
The important things to remember is that in order to use the -Include switch, you need an * at the end of the -Path value.
Explanation:
-Filter allows us to filter on names that contain two underscores separating three substrings.
-Include allows us to only list files ending in extensions .docx and .doc.
Rename-Item -NewName supports delayed script binding. This allows us use a scriptblock to perform any necessary operations for each piped object (each file).
Since the target files will always have two underscores, the .Split('_') method will result in an three index array delimited by the _. You have specified that you always want the second delimited substring and that is represented by index 1 ([1]).
The format operator (-f) puts the substring and extension together, completing the file name.

How to filter ProfileList with wildcards

I'm writing a script to help streamline the deletion of users profile list's on our Citrix servers. I cant figure out how to filter the ProfileImagePath by wildcards.
On our Windows 2008 servers in Regedit I can search and sort the profile list of users cia its Profileimagepath but this gets all the users, I cant seem to extend that filter to only bring back wildcard entries.
set-location | 'HKLM:\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\ProfileList' |% {Get-ItemProperty $_.pspath } | Select
profileImagePath=username*
What I am getting is the whole list of user profiles, or if I put the filter =username* entry I get an error, What I wanted to get was just the results of the wildcard, Ideally just the usernames I put in the wildcard are what I want returned.
The code you posted is utterly broken. Please go find a PowerShell tutorial before proceeding any further.
Set-Location does not produce output, and even if it did, piping cmdlet output into a string wouldn't do anything at all.
You must not wrap statements at arbitrary places like you do in your sample code.
The wrapped registry path string will preserve the embedded newline (thus not matching what you want it to match).
Putting the argument of the Select-Object statement on the next line without escaping the newline will run Select-Object without arguments and the arguments as a separate statement (which most likely will throw an error because the arguments aren't a valid statement by themselves).
= is an assignment operator in PowerShell. Comparison operators are -eq (equality), -like (wildcard matches), and -match (regular expression matches).
You need Where-Object for filtering pipelined objects, not Select-Object. The latter is for selecting object properties.
You propbably meant to do something like this:
$username = '...'
$profilePath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'
Get-ChildItem $profilePath | Where-Object {
(Get-ItemProperty $_.PSPath).ProfileImagePath -like "*${username}*"
}
or (simpler) like this:
$username = '...'
$profilePath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'
Get-ChildItem $profilePath |
Get-ItemProperty |
Where-Object { $_.ProfileImagePath -like "*${username}*" }