Removing Parts of a File Name based on a Delimiter - powershell

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.

Related

Powershell Remove Static Text From File Name

I'm very rusty on my Powershell. I have a folder with different types of files and I want to remove a constant string of text from each file. For example, I have 3 types of files each with the same text I want removed. But there are 300 of these in one folder.
file1(My Little Pony).nfo
file1(My Little Pony)-thumb.jpg
file1(My Little Pony).avi
file02(My Little Pony).nfo
file2(My Little Pony)-thumb.jpg
file002(My Little Pony).avi
I want to remove the text "(My Little Pony)"
It's going to be something like
$filepath ="C:\Folder 1"
foreach($file in $filepath){
# this is where I struggle
# rename $file take out "(My Little Pony)"
#
}
You are trying to iterate over the string "C:\Folder 1", but that is only the rootpath to where the files can be found.
You need to first get an array of FileInfo objects and iterate over that:
$files = Get-ChildItem -Path 'C:\Folder 1' -Filter '*(My Little Pony)*' -File
$files | Rename-Item -NewName { $_.Name -replace '\(My Little Pony\)'}
I'm using the regex -replace because that works Case-Insensitively as opposed to the string method .Replace()
By doing that, we need to escape the brackets (My Little Pony) with backslashes

Is there a way to grab data from this txt file in Powershell?

I am using Beyond Compare and I have gotten it to ouput if there are any differences into a txt file. However, I want to script this in powershell so that if there are no differences in files, the script will continue and do something else. However, I am not sure this is possible. I have looked through Select-String, but unsure if that will be able to do what I am looking for. Attached is what the txt file looks like when there are no differences as well as what it looks like when there are differences.
Is it possible to convert name, size, or modified in the txt file into a variable and then do a condition on whether it is null? Or is there is another way I can do what I am trying to achieve in Powershell? Thanks in advance.
If all you need to know is whether any differences were found, the following should do:
$noDiffs = '' -eq ((Get-Content -Raw report.txt) -split '\r?\n-+\r?\n')[1].Trim()
(Get-Content -Raw report.txt) -split '\r?\n-+\r?\n splits the entire input file by the the divider line (----...) following the table-header line, using a regex (regular expression).
[1] looks a the 2nd element of the resulting array, i.e., whatever comes after the divider line, trims any leading and trailing whitespace, and the result is tested for being the empty string.
With all respect to the question and the answer from #mklement0.
Using Beyond Compare in a PowerShell script is putting the cart before the horse.
There are a lot of cmdlets in PowerShell which would let you easily compare folders (and a lot more) without doing any text scraping.
As using Compare-Object togehter with Get-ChildItem for the given example:
Compare-Object (Get-ChildItem .\Test) (Get-ChildItem .\Test1) -Property Name, Length, LastWriteTime
If you want to do a recursive compare on the relative path, you can do:
Compare-Object (Get-ChildItem .\Test -Recurse -Name) (Get-ChildItem .\Test1 -Recurse -Name)
Note that the -Name parameter will only list relative path strings, if you also want to compare Length and LastWriteTime, You can do:
$TestFolder = 'C:\Test'
$Test1Folder = 'C:\Test1'
$TestFiles = Get-ChildItem $TestFolder -File -Recurse |
Select-Object *,#{N='RelativePath'; E={$_.FullName.SubString($TestFolder.Length)}}
$Test1Files = Get-ChildItem $Test1Folder -File -Recurse |
Select-Object *,#{N='RelativePath'; E={$_.FullName.SubString($Test1Folder.Length)}}
Compare-Object $TestFiles $Test1Files -Property RelativePath,Length,LastWriteTime

How would I specify a directory to run a PowerShell script that would edit file extensions?

I am new to PowerShell and new to IT. I've been asked by my boss to write a PowerShell script that will identify filenames that have no file extension and then change them to .PDF files. After doing some research online I've found a script that had a similar purpose and tried to tailor it to my needs:
$proj_files = Get-ChildItem | Where-Object {$_.Extension -eq "."}
ForEach ($file in $proj_files) {
$filenew = $file.Name + ".pdf"
Rename-Item $file $filenew
}
My first question is does the logic in this script make sense? Is "Extension -eq "." the correct syntax to specify a filename with no extension? My other thought was to use Extension -eq "null" or something similar. If I do need to use a null value, what would that look like? My other question is how would I specify a given directory for this script to search through, or would I even need to? My thought here would be to specify the path under Get-ChildItem, like so: $proj_files = Get-ChildItem -Path C:\Users\mthomas\Documents | Where-Object {$_.Extension -eq ".'} Does that seem correct? I am hesitant to test this out before getting a second opinion because I don't want to change every file extension on my computer or something stupid like that. Anyhow, thanks everyone for the help.
You can do something like the following to find files in a directory without an extension, and rename them to have a PDF extension:
$directory = "C:\Path\To\Directory"
Get-ChildItem -File $directory | Where-Object { -Not $_.Extension } | Foreach-Object {
$_ | Rename-Item -NewName "$($_.Name).pdf"
}
Let's break this down
$directory = "C:\Path\To\Directory"
This is where we set the directory we want to locate files without extensions in. It doesn't have to be set as a static variable but since you are just getting your feet wet with Powershell this keeps it simple.
Get-ChildItem -File $directory
Get-ChildItem is the cmdlet which is used to list directory contents (also aliased to gci, ls, and dir). -File tells it to only list files, and $directory references the directory we want to search from, which we set above. Note that Get-ChildItem might behave differently depending on the provider (for example, you can also use Get-ChildItem on a registry key), but if you are working with a filesystem path you do not need to worry about additional providers for this case.
|
Passes the previous output down the pipeline. This is a common operator in Powershell, but basically you can string commands together using it. You can read more about the pipeline at https://learn.microsoft.com/en-us/powershell/scripting/getting-started/fundamental/understanding-the-windows-powershell-pipeline?view=powershell-6
Where-Object { -Not $_.Extension }
Where-Object evaluates a condition on one or more items, and filters out items that do not meet the condition. Since Get-ChildItem can return one or more files, we use the -Not operator in the ScriptBlock (denoted by {} and make sure that there is no extension on the file. $_, or $PSItem, is a special variable used by the pipeline, in this case $_ equals each item returned by Get-ChildItem. The Extension property exists on files returned by Get-ChildItem, and will be blank, or evaluated as $False. So filtering on -Not $_.Extension is the same as saying to only match objects that are missing a file extension. Where-Object can be read about in more detail here: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/where-object?view=powershell-6
Foreach-Object { SCRIPTBLOCK }
Similar to Where-Object, but runs code for each object in the pipeline rather than evaluating and filtering out objects which don't match a condition. In this case, we pipe the each file without an extension to Rename-Item, which I'll break down further below. More information on Foreach-Object can be read about here: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object?view=powershell-6
$_ | Rename-Item -NewName "$($_.Name).pdf"
Rename the current file in the Foreach-Object block to the new name with .pdf appended. The "$( ... )" is called a sub-expression, which is a string interpolation technique that lets you run a command within a string, and make its output part of the string. You could achieve the same effect by doing $_ | Rename-Item -NewName ( $_.Name + ".pdf" ) which just adds a .pdf to the end of the current name.
Summary
The pipeline is a very powerful tool in Powershell, and is key to writing efficient and less bloated scripts. It might seem complex at first, but the more you use it the less daunting it will seem. I highly suggest reading the additional documentation I linked to above as it should help fill in any gaps I may have missed in my explanations above.
To simplify the breakdown above, the command does this, in this order: Gets all files in the specified directory, selects only the files that do not have an extension, then renames each file found without an extension to have a .pdf at the end.
The logic in the script - the overall shape - makes understandable sense, but is not right for it to work as you intend.
Testing on my computer here:
new-item -ItemType File -Name 'test'
get-item test | format-list *
get-item test | foreach { $_.extension; $_.Extension.length; $_.extension.GetType().name }
a file with no extension shows up with an empty string (blank content, length 0, type String, so your where-object { $_.Extension -eq "." } needs to be looking for "" instead of ".".
But:
Get-ChildItem | Where-Object { $_.Extension -eq '' }
shows me some folders as well, because they also have no extension in their name, so you might want Get-ChildItem -File to restrict it to just files.
how would I specify a given directory for this script to search through, or would I even need to?
It would run in the current directory, whichever shows up in your prompt PS C:\wherever> so if you need it to run somewhere else, yes you'd need to change to that folder or specify in get-childitem -LiteralPath 'c:\path\to\wherever'. You haven't mentioned subfolders, if you need those included, get-childitem -Recurse switch as well.
Speaking of subfolders, your $filenew = $file.Name + ".pdf" only makes sense in the current directory, I think it would work better if you used the full filename including path, so they definitely get renamed in the same place they were found $filenew = $file.FullName + ".pdf"
Is "Extension -eq "." the correct syntax to specify a filename with no extension?
Being careful here, what you wrote in your question was correct syntax but incorrect string content. What you've written here with quotes on the left of Extension is incorrect syntax.
My other thought was to use Extension -eq "null" or something similar. If I do need to use a null value, what would that look like?
And being careful here, "null" is not a null value, it's a string containing the four letter word 'null'.
You don't need to use a null value here, normally if you do it looks like $null, but in this case you could use where-object { [string]::IsNullOrEmpty($_.Extension) } but there's no benefit to it, I think.
And, as a stylistic choice, both "" and '' are strings, but "" can contain variables and sub-expressions, so if you have plain text it's a neat habit to use '' for it because it makes it clear to the reader that you intend there to be nothing special happening in this string.
Then your code, with parameter names given, looks more like:
$proj_files = Get-ChildItem -LiteralPath 'C:\Users\mthomas\Documents' |
Where-Object {$_.Extension -eq '.'}
foreach ($file in $proj_files)
{
$filenew = $file.FullName + '.pdf'
Rename-Item -LiteralPath $file.FullName -NewName $filenew
}
If you want to see what it will do, use -WhatIf on the end of Rename-Item:
Rename-Item -LiteralPath $file.FullName -NewName $filenew -WhatIf
Then it won't make the changes, just tell you what it would do.
I am hesitant to test this out before getting a second opinion because I don't want to change every file extension on my computer or something stupid like that
Sensible. But internet people are going to tell you to test their code before running it, because ultimately it's your responsibility to safeguard your files, rather than trust random code from the internet, so having test folders, having a spare machine, having a good backup, playing with PowerShell in pieces until you are happy with what they do, they're all good habits to get into as well.

Powershell - Match ID's in a text file against filenames in multiple folders

I need to search through 350,000 files to find any that contains certain patterns in the filename. However, the list of patterns (id numbers) that it needs to match is 1000! So I would very much like to be able to script this, because they were originally planning on doing it manually...
So to make it clearer:
Check each File in folder and all subfolders.
If the filename contains any of the IDs in the text file then move it to another file
Otherwise, ignore it.
So I have the basic code that works with a single value:
$name = Get-Content 'C:\test\list.txt'
get-childitem -Recurse -path "c:\test\source\" -filter "*$name*" |
move-item -Destination "C:\test\Destination"
If I change $name to point to a single ID, it works, if I have a single ID in the txt file, it works. Multiple items in a list:
1111111
2222222
3333333
It fails. What am I doing wrong? How can I get it to work? I'm still new to powershell so please be a little more descriptive in any answers.
Your test fails because it is effectively trying to do this (using your test data).
Get-ChildItem -Recurse -Path "c:\test\source\" -filter "*1111111 2222222 3333333*"
Which obviously does not work. It is squishing the array into one single space delimited string. You have to account for the multiple id logic in a different way.
I am not sure which of these will perform better so make sure you test both of these with your own data to get a better idea of execution time.
Cycle each "filter"
$filters = Get-Content 'C:\test\list.txt'
# Get the files once
$files = Get-ChildItem -Recurse -Path "c:\test\source" -File
# Cycle Each ID filter manually
$filters | ForEach-Object{
$singleFilter
$files | Where-Object{$_.Name -like "*$singleFilter*"}
} | Move-Item -Destination "C:\test\Destination"
Make one larger filter
$filters = Get-Content 'C:\test\list.txt'
# Build a large regex alternative match pattern. Escape each ID in case there are regex metacharacters.
$regex = ($filters | ForEach-Object{[regex]::Escape($_)}) -join "|"
# Get the files once
Get-ChildItem -Recurse -path "c:\test\source" -File |
Where-Object{$_.Name -match $regex} |
Move-Item -Destination "C:\test\Destination"
try following this tutorial on how to use get-content function. Looks like when you have a multiple line file, you get an array back. you then have to iterate through your array and use the logic you used for only one item

In function repeat an action for each entered parameter

My main script run once gci on a specified drive via -path parameter , then it does multiple different tables from this output. Here below is a part of my script which does a specific table from an directory specified via -folder parameter, for example :
my-globalfunction -path d:\ -folder d:\folder
It work fine, but only for one entered folder path, the goal of this script is that user can enter multiple folders path and get a tables for each entered -folder parameter value, like this :
This clause in your Where-Object would be the issue:
$_.FullName.StartsWith($folder, [System.StringComparison]::OrdinalIgnoreCase)
The array of folders passed are most likely being cast as one long string which would never match. I had a regex solution posted but remembered a simpler way after looking at what your logic was trying to do.
Simpler Way
Even easier way is to put this information right into Get-ChildItem since it accepts string arrays for -Path. This way I don't think you even need to have 2 parameters since you never again use the results from $fol anyway. Based on the assumption that you were looking for all subfolders of $folder
$gdfolders = Get-ChildItem -Path $folder -Recurse -Force | Where-Object{$_.psiscontainer}
That would return all subfolders of the paths provided. If you have PowerShell 3.0 or higher this would even be easier.
$gdfolders = Get-ChildItem -Path $folder -Recurse -Force -Directory
Update from comments
The code you have displayed is incomplete which is what lead me to the solution that you see above. If you do use the variable $fol somewhere else that you do not show lets go back to my earlier regex solution which would work better in place with what you already have.
$regex = "^($(($folder | ForEach-Object{[regex]::Escape($_)}) -join "|")).+"
....
$gdfolders = $fol | Where-Object{($_.Attributes -eq "Directory") -and ($_.FullName -match $regex)}
What this will do is build a regex compare string with what I will assume is the logic of locate folders that begin with either of paths passed.
Using your example input of "d:\folder1", "d:\folder2" the variable $regex would work out to ^(d:\\folder1|d:\\folder2). The proper characters, like \, are escaped automatically by the static method [regex]::Escape which is applied to each element. We then use -join to place a pipe which, in this regex capture group means match whats on the left OR on the right. For completeness sake we state that the match has to occur at the beginning of the path with the caret ^ although this is most likely redundant. It would match paths that start with either "d:\folder1" or "d:\folder2". At the end of the regex string we have .+ which means match 1 to more characters. This should ensure we dont match the actual folder "d:\folder1" but meerly its children
Side Note
The quotes in the line with ’Size (MB)’ are not the proper ones which are '. If you have issues around that code consider changing the quotes.