How find a file with part of his name in PowerShell - powershell

I'm new to PowerShell and on StackOverflow.
I'm trying to write a script which gives me the Total Path to a *.pdf file,
My File Name is in a variable (I get it before with previous part of code) for example : $myVariable.Nom
After I try to find my file with this:
Get-ChildItem -Path "c:\myPath\" -Filter $myVariable.Nom
But it doesn't work. I think I have a mistake where I try to filter with my File Name in the ChildItem command but don't know how to use it correctly.
The goal is (if myVariable.Nom = "TOTO" for example) to have c:\myPath\TOTO.pdf
Can anyone help me ?
Thanks.

The -Filter parameter on Get-ChildItem supports a wildcard search using * or ?. Your code is pretty close to what you're looking for, you just need to add *.
Get-ChildItem -Path "c:\myPath\" -Filter "*$($myVariable.Nom)*"
Note that I'm using * at the beginning and end of the variable, meaning that the filter should search for any Item that contains the string in your variable.
In your example, you know that the File Name is TOTO so you could use * only at the end of the Filter:
-Filter "$($myVariable.Nom)*"
Remember that you can add the -Recurse flag on Get-ChildItem if you want to search recursively.
Now if you want to get the FullName (Total Path) you can use any of the examples below:
# Example 1
(Get-ChildItem -Path "c:\myPath\" -Filter "*$($myVariable.Nom)*").FullName
# Example 2
$var = Get-ChildItem -Path "c:\myPath\" -Filter "*$($myVariable.Nom)*"
$var.FullName
# Example 3
Get-ChildItem -Path "c:\myPath\" -Filter "*$($myVariable.Nom)*" |
Select-Object -Expand FullName
Edit
Following your second question, how to deal with a CSV with no headers:
$path = 'C:\path\to\csv.csv'
# Note: I'm using -Delimiter ';' because as you have shown,
# you are using semicolon as delimiter, however, if the delimiter
# are commas there is no need to use it.
$csv = Import-Csv $path -Delimiter ';' -Header 'Name','Qte'
# Now you can loop through it to find the files:
foreach($file in $csv)
{
$var = Get-ChildItem -Path "c:\myPath\" -Filter "*$($file.Name)*"
0..$file.Qte | foreach-object {
$var.FullName
}
}

Thank you for your answers : It's work fine !!
Now i want to improve it a litle bit : In my CSV i have 2 column : in the previous example i called the first column "Name" this is why i used myVariable.Name) for example:
Name;Qte
TOTO;1
TITI;3
Now how i can select the first column if i didn't have name on first line ?
file like this one for example :
TOTO;1
TITI;3
Thank you

Related

How to take a list of partial file names and return a list of the full file names with PowerShell

I’m wondering how to take a list of partial document names and return a list of the full document names with PowerShell.
I have ton of documents to do this with. We have a naming scheme: HG-xx-xx-###
The full naming scheme for the actual files is: HG-xx-xx-###.x.x_File_Name
I have a lot of different lists of file names like so:
HG-SP-HG-001
HG-WI-BE-005
HG-GD-BB-043
I'm trying to have program return a list of the full file names like so:
HG-SP-HG-001.1.6_Example
HG-WI-BE-005.1.0_Example
HG-GD-BB-043.1.1_Example
I've included both methods I've tried. I give it a list or even just one partial file name and I get nothing back.
I've tried two different ways and I'm at the end of my programming and googling capabilities, any ideas?
$myPath = 'P:\'
$_DocList = READ-HOST "Enter list of document ID's"
$_DocList = $_DocList.Split(',').Split(' ')
#Here I'm not sure if I should do it like so:
$output =
ForEach($_Doc in $_DocList)
{
$find = gci $myPath -Recurse |where{$_.name -contains $($_Doc)}
Write-Host "$find"
}
$output | clip
#or like this:
$_DocList | ForEach-Object
{
gci -Path $myPath -Filter $_ -Recurse
$info = Get-ChildItem $_.FullName | Measure-Object
if ($info.Count -ne 0) {
Write-Output "$($_.Name)"
}
} | clip
Doug Maurer's helpful answer shows a solution based on a wildcard pattern based to the -Filter parameter.
Since this parameter only accepts a single pattern, the Get-ChildItem -Recurse call must be called multiple times, in a loop.
However, since you're using -Recurse, you can take advantage of the -Include parameter, which accepts multiple patterns, so you can get away with one Get-ChildItem call.
While for a single Get-ChildItem call -Filter performs better than -Include, a single Get-ChildItem -Include call with an array of pattern is likely to outperform multiple Get-ChildItem -Filter calls, especially with many patterns.
# Sample name prefixes to search for.
$namePrefixes = 'HG-SP-HG-001', 'HG-WI-BE-005', 'HG-GD-BB-043'
# Append '*' to all prefixes to form wildcard patterns: 'HG-SP-HG-001*', ...
$namePatterns = $namePrefixes -replace '$', '*'
# Combine Get-ChildItem -Recurse with -Include and all patterns.
# .Name returns the file name part of all matching files.
$names = (Get-ChildItem $myPath -File -Recurse -Include $namePatterns).Name
Maybe something like this?
$docList = #('HG-SP-HG-*','HG-WI-BE-*','HG-GD-BB-*')
foreach($item in $docList)
{
$check = Get-ChildItem -Filter $item P:\ -File
if($check)
{
$check
}
}
Maybe something like this?
$docList = #('HG-SP-HG','HG-WI-BE','HG-GD-BB')
$docList | Get-ChildItem -File -Filter $_ -Recurse | select Name
When using the filter with partial names you'll need to specify wildcard
$names = 'HG-SP-HG','HG-WI-BE','HG-GD-BB'
$names | Foreach-Object {
Get-ChildItem -File -Filter $_* -Recurse
}
And if you only want the full path back, simply select it.
$names = 'HG-SP-HG','HG-WI-BE','HG-GD-BB'
$names | Foreach-Object {
Get-ChildItem -File -Filter $_* -Recurse
} | Select-Object -ExpandProperty FullName
If you have an established pattern of what the files look like, why not regex it?
# Use these instead to specify a docID
#$docID = "005"
#pattern = "^HG(-\w{2}){2}-$docID"
$pattern = "^HG(-\w{2}){2}-\d{3}"
Get-ChildItem -Path "P:\" -Recurse | ?{$_ -match $pattern}
Granted, there may be more efficient ways to do this, but it should be quick enough for a few thousand files.
EDIT: This is the breakdown of the regex pattern's hieroglyphics.
^ Start at the beginning
HG literal characters "HG"
(-\w{2})
( start of a grouping
- literal "-" character (hyphen)
\w{2}
\w any word character
{2} exactly 2 times
) End of the grouping
{2} exactly 2 times
- literal "-" character (hyphen)
\d any digit 0 through 9
{3} Exactly 3 times

Match string with specific numbers from array

I want to create a script that searches through a directory for specific ".txt" files with the Get-ChildItem cmdlet and after that it copies the ".txt" to a location I want. The hard part for me is to extract specific .txt files string from the array. So basically I need help matching specific files names in the array. Here is an example of the array I'm getting back with the following cmdlet:
$arrayObject = (Get-ChildItem -recurse | Where-Object { $_.Name -eq "*.txt"}).Name
The arrayobject variable is something like this:
$arrayobject = "test.2.5.0.txt", "test.1.0.0.txt", "test.1.0.1.txt",
"test.0.1.0.txt", "test.0.1.1.txt", "test.txt"
I want to match my array so it returns the following:
test.2.5.0.txt, test.1.0.0.txt, test.1.0.1.txt
Can someone help me with Regex to match the above file names from the $arrayObject?
As you already add the -Recurse parameter to Get-ChildItem, you can also use the -Include parameter like this:
$findThese = "test.2.5.0.txt", "test.1.0.0.txt", "test.1.0.1.txt"
$filesFound = (Get-ChildItem -Path 'YOUR ROOTPATH HERE' -Recurse -File -Include $findThese).Name
P.S. without the -Recurse parameter you need to add \* to the end of the rootfolder path to be able to use -Include
Maybe something like:
$FileList = Get-ChildItem -path C:\TEMP -Include *.txt -Recurse
$TxtFiles = 'test1.txt', 'test3.txt', 'test9.txt'
Foreach ($txt in $TxtFiles) {
if ($FileList.name -contains $txt) {Write-Host File: $Txt is present}
}
A general rule: Filter as left as possible! Less objects to be processed, less resources to be used, faster to be processed!
Hope it helps!
Please try to clarify what the regex should match.
I created a regex which matches out of the given filenames only the files you wanted to retrieve:
"*.[1-9].[0-9].[0-9].txt"
You can tryout the small check I wrote.
ForEach($file in $arrayobject){
if($file -LIKE "*.[1-9].[0-9].[0-9].txt"){
Write-Host $file
}}
I think the "-LIKE" operator would be better to check if a string matches a regex.
Let me know if this helps.
Sorry for the late reply. Just got back in the office today. My question has been misinterpreted but that's my fault. I wasn't clear what I really want to do.
What I want to do is search through a directory and retrieve/extract in my case the (major)version of a filename. So in my case file "test.2.5.0.txt" would be version 2.5.0. After that I will get the MajorVersion and that's 2. Then in an If statement I would check if it's greater or equal to 1 and then copy it to a specific destination. To add some context to it. It's nupkg files and not txt. But I figured it out. This is code:
$sourceShare = "\\server1name\Share\txtfilesFolder"
destinationShare = "\\server2name\Share\txtfilesFolder"
Get-ChildItem -Path $sourceShare `
-Recurse `
-Include "*.txt" `
-Exclude #("*.nuspec", "*.sha512") `
| Foreach-Object {
$fileName = [System.IO.Path]::GetFileName($_)
[Int]$majorVersion = (([regex]::Match($fileName,"(\d+(.\d+){1,})" )).Value).Split(".")[0]
if ($majorVersion -ge 1)
{
Copy-Item -Path $_.FullName `
-Destination $destinationShare `
-Force
}
}
If you have anymore advice. Let me know. I would be great to extract the major version without using the .Split method
Grtz

Powershell capture first filename from a folder

Newbie to powershell
I need to capture first file name from a directory. However my current script captures all file names. Please suggest changes to my code below.
# Storing path of the desired folder
$path = "C:\foo\bar\"
$contents = Get-ChildItem -Path $path -Force -Recurse
$contents.Name
The result is following
test-01.eof
test-02.eof
test-03.eof
i just want one file (any) from this list. So expected result should be
test-01.eof
You could use Select-Object with the -first switch and set it to 1
$path = "C:\foo\bar\"
$contents = Get-ChildItem -Path $path -Force -Recurse -File | Select-Object -First 1
I've added the -File switch to Get-ChildItem as well, since you only want to return files.
$path = "C:\foo\bar\"
$contents = Get-ChildItem -Path $path -Force -Recurse
$contents # lists out all details of all files
$contents.Name # lists out all files
$contents[0].Name # will return 1st file name
$contents[1].Name # will return 2nd file name
$contents[2].Name # will return 3rd file name
The count starts from 0. So $contents here is an array or a list and whatever integer you mention in [] is the position of the item in that array/list. So when you type $contents[9], you are telling powershell that get the 9th item from the array $contents. This is how you iterate through a list. In most programming languages the count begins from 0 and not 1. It is a bit confusing for a someone who is entering the coding world but you get used to it.
Please use below command which is simple and helpful. Adding recurse will only put slight load on the machine or powershell (when the code is huge and it has been used somewhere)
Storing path of the desired folder
$path = "C:\foo\bar\"
$contents = Get-ChildItem -Path $path | sort | Select-Object -First 1
$contents.Name
Output Will be as as expected:
test-01.eof
Select-Object -First will select each object (or row) and provide first row data as an output if you put as 1

How to get Get-ChildItem to handle path with non-breaking space

I have the following code that works for most files. The input file (FoundLinks.csv) is a UTF-8 file with one file path per line. It is full paths of files on a particular drive that I need to process.
$inFiles = #()
$inFiles += #(Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv")
foreach ($inFile in $inFiles) {
Write-Host("Processing: " + $inFile)
$objFile = Get-ChildItem -LiteralPath $inFile
New-Object PSObject -Prop #{
FullName = $objFile.FullName
ModifyTime = $objFile.LastWriteTime
}
}
But even though I've used -LiteralPath, it continues to not be able to process files that have a non-breaking space in the file name.
Processing: q:\Executive\CLC\Budget\Co  2018 Budget - TO Bob (GA Prophix).xlsx
Get-ChildItem : Cannot find path 'Q:\Executive\CLC\Budget\Co  2018 Budget - TO Bob (GA Prophix).xlsx'
because it does not exist.
At ListFilesWithModifyTime.ps1:6 char:29
+ $objFile = Get-ChildItem <<<< -LiteralPath $inFile
+ CategoryInfo : ObjectNotFound: (Q:\Executive\CL...A Prophix).xlsx:String) [Get-ChildItem], ItemNotFound
Exception
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
I know my input file has the non-breaking space in the path because I'm able to open it in Notepad, copy the offending path, paste into Word, and turn on paragraph marks. It shows a normal space followed by a NBSP just before 2018.
Is PowerShell not reading in the NBSP? Am I passing it wrong to -LiteralPath? I'm at my wit's end. I saw this solution, but in that case they are supplying the path as a literal in the script, so I can't see how I could use that approach.
I've also tried: -Encoding UTF8 parameter on Get-Content, but no difference.
I'm not even sure how I can check $inFile in the code just to confirm if it still contains the NBSP.
Grateful for any help to get unstuck!
Confirmed that $inFile has NBSP
Thank you all! As per #TheMadTechnician, I have updated the code like this, and also reduced my input file to only the one file having a problem.
$inFiles = #()
$inFiles += #(Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv" -Encoding UTF8)
foreach ($inFile in $inFiles) {
Write-Host("Processing: " + $inFile)
# list out all chars to confirm it has an NBSP
$inFile.ToCharArray()|%{"{0} -> {1}" -f $_,[int]$_}
$objFile = Get-ChildItem -LiteralPath $inFile
New-Object PSObject -Prop #{
FullName = $objFile.FullName
ModifyTime = $objFile.LastWriteTime
}
}
And so now I can confirm that $inFile in fact still contains the NBSP just as it gets passed to Get-ChildItem. Yet Get-ChildItem says the file does not exist.
More I've tried:
Same if I use Get-Item instead of Get-ChildItem
Same if I use -Path instead of -LiteralPath
Windows explorer and Excel can deal with the file successfully.
I'm on a Windows 7 machine, Powershell 2.
Thanks again for all the responses!
It's still unclear why Sandra's code didn't work: PowerShell v2+ is capable of retrieving files with paths containing non-ASCII characters; perhaps a non-NTFS filesystem with different character encoding was involved?
However, the following workaround turned out to be effective:
$objFile = Get-ChildItem -Path ($inFile -replace ([char] 0xa0), '?')
The idea is to replace the non-breaking space char. (Unicode U+00A0; hex. 0xa) in the input file path with wildcard character ?, which represents any single char.
For Get-ChildItem to perform wildcard matching, -Path rather than -LiteralPath must be used (note that -Path is actually the default if you pass a path argument positionally, as the first argument).
Hypothetically, the wildcard-based paths could match multiple files; if that were the case, the individual matches would have to be examined to identify the specific match that has a non-breaking space in the position of the ?.
Get-ChildItem is for listing children so you would be giving it a directory, but it seems you are giving it a file, so when it says it cannot find the path, it's because it can't find a directory with that name.
Instead, you would want to use Get-Item -LiteralPath to get each individual item (this would be the same items you would get if you ran Get-ChildItem on its parent.
I think swapping in Get-Item would make your code work as is.
After testing, I think the above is in fact false, so sorry for that, but I will leave the below in case it's helpful, even though it may not solve your immediate problem.
But let's take a look at how it can be simplified with the pipeline.
First, you're starting with an empty array, then calling a command (Get-Content) which likely already returns an array, wrapping that in an array, then concatenating it to the empty one.
You could just do:
$inFiles = Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv"
Yes, there is a chance that $inFiles will contain only a single item and not an array at all.
But the nice thing is that foreach won't mind one bit!
You can do something like this and it just works:
foreach ($string in "a literal single string") {
Write-Host $string
}
But Get-Item (and Get-ChildItem for that matter) accept pipeline input, so they accept multiple items.
That means you could do this:
$inFiles = Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv" | Get-Item
foreach ($inFile in $inFiles) {
Write-Host("Processing: " + $inFile)
New-Object PSObject -Prop #{
FullName = $inFile.FullName
ModifyTime = $inFile.LastWriteTime
}
}
But even more than that, there is a pipeline-aware cmdlet for processing items, called ForEach-Object, to which you pass a [ScriptBlock], in which $_ represents the current item, so we could do it like this:
Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv" |
Get-Item |
ForEach-Object -Process {
Write-Host("Processing: " + $_)
New-Object PSObject -Prop #{
FullName = $_.FullName
ModifyTime = $_.LastWriteTime
}
}
All in one pipeline!
But further, you're creating a new object with the 2 properties you want.
PowerShell has a nifty cmdlet called Select-Object which takes an input object and returns a new object containing only the properties you want; this would make for a cleaner syntax:
Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv" |
Get-Item |
Select-Object -Property FullName,LastWriteTime
This is the power of the the pipeline passing real objects from one command to another.
I realize this last example does not write the processing message to the screen, however you could re-add that in if you wanted:
Get-Content -Path "C:\Users\sw_admin\FoundLinks.csv" |
Get-Item |
ForEach-Object -Process {
Write-Host("Processing: " + $_)
$_ | Select-Object -Property FullName,LastWriteTime
}
But you might also consider that many cmdlets support verbose output and try to just add -Verbose to some of your existing cmdlets. Sadly, it won't really help in this case.
One final note, when you pass items to the filesystem cmdlets via pipeline, the parameter they bind to is in fact -LiteralPath, not -Path, so your special characters are still safe.
I just run into the same issue. Looks like get-childitem ak gci expects the path in unicode (UTF-16). So either convert the csv file into unicode or convert the lines that include the path as unicode within your script.
Testet on PS 5.1.22621.608

Counting files that start with the same characters

I'm trying to get a count of all files in a directory whose first few characters match a string. Here is the function:
function CountMatchingID($d1,$s){
$d = Get-ChildItem -filter $s -path $d1 | Measure-Object
return $d.count
}
I'm passing in the directory and a variable with a wildcard. It is returning the total count of files in the directory. However if I change the $s inside the function to the value of that variable like below, it will return the accurate number of files that have that beginning.
function CountMatchingID($d1,$s){
$d = Get-ChildItem -filter "201*" -path $d1 | Measure-Object
return $d.count
}
The files are employee id photos with employee numbers at the beginning and I'm trying to automate searching a csv file to rename them to the username so we can dump them into a directory for Jabber to show the photos.
I'm open to suggestions about a better way to accomplish this as well. I'm fairly new to Powershell so any feedback is welcome.
Issue might be what you are passing as $s for which you don't show an example. This works as expected for me
function CountMatchingID($d1,$s){
Get-ChildItem -filter "$s*" -path $d1 | Measure-Object | Select -ExpandProperty Count
}
I added the asterisk into the filter automatically so that it can just be assumed.
A sample call would look like this
CountMatchingID "c:\temp" "test"